Skip to content

Commit

Permalink
Merge pull request #6 from GregTheGreek/feat/ganache
Browse files Browse the repository at this point in the history
Major updates
  • Loading branch information
GregTheGreek committed Feb 23, 2022
2 parents 2dc2a32 + fba283f commit c33afdc
Show file tree
Hide file tree
Showing 19 changed files with 662 additions and 142 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -60,7 +60,8 @@
"cors": "^2.8.5",
"ethers": "^5.5.4",
"express": "^4.17.2",
"ganache": "^7.0.0-alpha.2",
"ganache": "^7.0.2",
"inquirer": "^8.2.0",
"prompts": "^2.4.2"
}
}
41 changes: 41 additions & 0 deletions src/engine.ts
@@ -0,0 +1,41 @@
import ethers, { Transaction } from "ethers";
import { GanacheEngine } from "./rules/ganache";
import { JsonRpcProvider } from "@ethersproject/providers";

export interface Rule {
moduleName: string;
useGanache: boolean;
provider: ethers.providers.JsonRpcProvider;
run(tx: Transaction): Promise<void>;
}

export class RuleEngine {
rules: Rule[] = [];
provider: JsonRpcProvider;
ganache: GanacheEngine;

constructor(provider: JsonRpcProvider, rules: Rule[] = []) {
this.provider = provider;
// Create ganache instancing for forking features
this.ganache = new GanacheEngine(provider.connection.url)
rules.map(x => this.addRule(x));
};

addRule(rule: Rule): void {
// If rule requires ganache forking, swap provider
// @TODO - ganache may need to be instatiated in the rule itself
// I think this is actually causing the fork to happen too early.
// We should fork on demand
if (rule.useGanache) {
rule.provider = this.ganache.provider;
}
this.rules.push(rule);
}

async run(tx: Transaction): Promise<void> {
for (let i=0; i < this.rules.length; i++) {
await this.rules[i].run(tx);
console.log("\n")
}
}
}
86 changes: 48 additions & 38 deletions src/index.ts
Expand Up @@ -4,56 +4,66 @@ import express from 'express';
const cors = require("cors");

import {Proxy} from "./proxy";
import { RuleEngine } from './rules/engine';
import { EnsRules } from './rules/generic/ens';
import { ContractVerificationRules } from './rules/generic/contractVerification';
import { RuleEngine } from './engine';
import Rules from './rules';
import { selectRules } from './ruleSelector';

// CLI
const program = new Command();
program
.option('-r --rpc <url>', 'RPC URL to proxy to', 'http://localhost:8545/')
.option('-p --port <number>', 'Port number to listen on', '9545');

program.parse(process.argv);
const options = program.opts();

// Setup proxy and rules
const provider = new ethers.providers.JsonRpcProvider({ url: options.rpc });
const rules = [
new EnsRules(provider),
new ContractVerificationRules(provider),
]
const ruleEngine = new RuleEngine(rules);
const proxy = new Proxy(provider, ruleEngine);

// Proxy server Logic
const app = express();
app.use(express.json());
app.use(cors());

app.post('/', async (req, res) => {
const { id, method, params } = req.body;
try {
const result = await proxy.rpcHandler(method, params);
res.json({
jsonrpc: "2.0",
id,
result
});
} catch(e) {
if((e as any)?.body !== undefined) {
const { error } = JSON.parse((e as any).body);
(async () => {

// Instantiate provider from cli
const provider = new ethers.providers.JsonRpcProvider({ url: options.rpc });

// Prompt user to select rules
const selectedRules = await selectRules();
const rules = selectedRules.map((name:string) => {
// Not sure how to get around typings for this.
// @ts-ignore
return new Rules[name](provider);
})

// Setup rule engine and proxy
const ruleEngine = new RuleEngine(provider, rules);
const proxy = new Proxy(provider, ruleEngine);

// Proxy server configuration
const app = express();
app.use(express.json());
app.use(cors());

// Proxy server routes
app.post('/', async (req, res) => {
const { id, method, params } = req.body;
try {
const result = await proxy.rpcHandler(method, params);
res.json({
jsonrpc: "2.0",
id,
error
result
});
} else {
console.log(e);
} catch(e) {
if((e as any)?.body !== undefined) {
const { error } = JSON.parse((e as any).body);
res.json({
jsonrpc: "2.0",
id,
error
});
} else {
console.log(e);
}
}
}
});
});

app.listen(parseInt(options.port), () => {
console.log(`Listening on port ${options.port}`);
});

app.listen(parseInt(options.port), () => {
console.log(`Listening on port ${options.port}`);
});
})();
2 changes: 1 addition & 1 deletion src/proxy.ts
@@ -1,5 +1,5 @@
import ethers from "ethers";
import { RuleEngine } from "./rules/engine";
import { RuleEngine } from "./engine";
const prompts = require('prompts');

export class Proxy {
Expand Down
15 changes: 15 additions & 0 deletions src/ruleSelector.ts
@@ -0,0 +1,15 @@
import Rules from "./rules";
const inquirer = require("inquirer");

export const selectRules = async (): Promise<string[]> => {
const choices = Object.keys(Rules);
const answer = await inquirer.prompt([
{
type: "checkbox",
message: "Select the rules you want to use:",
name: "modules",
choices
}
]);
return answer.modules;
}
6 changes: 3 additions & 3 deletions src/rules/dapps/index.ts
@@ -1,7 +1,7 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import { Transaction } from "ethers";

import { Rule } from "../engine";
import { Rule } from "../../engine";
import { isContract, logModuleHeader } from "../utils";
import { knownContracts } from "./knownContracts";

Expand All @@ -11,15 +11,15 @@ import { knownContracts } from "./knownContracts";
*/
export class ContractVerificationRules implements Rule {
moduleName = "Dapp Rule Module";

useGanache: boolean = false;
provider: JsonRpcProvider;

constructor(provider: JsonRpcProvider) {
this.provider = provider;
}

async run(tx: Transaction): Promise<void> {
if (tx.to && isContract(this.provider, tx.to)) {
if (tx.to && await isContract(this.provider, tx.to)) {
const info = knownContracts[tx.to];
if (info) {
this.log(`Running [${info.name}]`);
Expand Down
4 changes: 2 additions & 2 deletions src/rules/dapps/uniswapv2.ts
@@ -1,15 +1,15 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import { Transaction } from "ethers";

import { Rule } from "../engine";
import { Rule } from "../../engine";
import { logModuleHeader } from "../utils";

/**
* The Uniswapv2 Modules describes various opperations made on uniswap.
*/
export class Uniswapv2Rules implements Rule {
moduleName = "UniswapV2 Rule Module";

useGanache: boolean = false;
provider: JsonRpcProvider;

constructor(provider: JsonRpcProvider) {
Expand Down
26 changes: 0 additions & 26 deletions src/rules/engine.ts

This file was deleted.

71 changes: 71 additions & 0 deletions src/rules/erc/erc20.ts
@@ -0,0 +1,71 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import { Contract, Transaction } from "ethers";
import { Rule } from "../../engine";
import { isContract, logModuleHeader, serializeTx } from "../utils";
import ethers from "ethers";

import { Erc20Interface } from "./interfacces/erc20";

/**
* The ERC20 module attempts to inform the user about a given token contract.
*/
export class Erc20Rules implements Rule {
moduleName = "ERC20 Rule Module";
useGanache: boolean = true;
provider: JsonRpcProvider;
contract: Contract | null;

constructor(provider: JsonRpcProvider) {
this.provider = provider;
this.contract = null;
}

async run(tx: Transaction): Promise<void> {
if (!tx.from) return;
if (!tx.to) return;
if (!(await isContract(this.provider, tx.to))) return;

this.contract = new ethers.Contract(tx.to, Erc20Interface, this.provider);

if (!(await this.isErc20(tx.from))) {
this.log("Not an erc20 contract!");
return;
}

await this.checkBalance(tx);

}

private log(msg: string): void {
logModuleHeader(this.moduleName);
console.log(msg);
}

private async checkBalance(tx: Transaction): Promise<void> {
if (!this.contract) return;
const balanceBefore: ethers.BigNumber = await this.contract.balanceOf(tx.from);
const txResponse = await this.provider.sendTransaction(serializeTx(tx));
await txResponse.wait();
const balanceAfter: ethers.BigNumber = await this.contract.balanceOf(tx.from);

if (balanceAfter.gt(balanceBefore)) {
this.log("Increase");
} else if (balanceAfter.lt(balanceBefore)) {
this.log("Decrease");
} else {
this.log("No change");
}
}

private async isErc20(address: string): Promise<boolean> {
if (!this.contract) return false;
try {
await this.contract.name();
await this.contract.symbol();
await this.contract.balanceOf(address);
return true
} catch (e) {
return false
}
}
}
1 change: 1 addition & 0 deletions src/rules/erc/index.ts
@@ -0,0 +1 @@
export * from "./erc20";

0 comments on commit c33afdc

Please sign in to comment.