Setup blockchain snapshotting before/after every test, implemented unit tests for exchangeWrapper.isValidSignature

This commit is contained in:
Fabio Berger 2017-05-26 17:12:22 +02:00
parent f4bf9fc423
commit 555bac19cb
7 changed files with 204 additions and 0 deletions

View File

@ -36,6 +36,7 @@
"devDependencies": {
"@types/bignumber.js": "^4.0.2",
"@types/chai": "^3.5.2",
"@types/chai-as-promised": "0.0.30",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.64",
"@types/mocha": "^2.2.41",
@ -43,6 +44,7 @@
"awesome-typescript-loader": "^3.1.3",
"bignumber.js": "^4.0.2",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-bignumber": "^2.0.0",
"copyfiles": "^1.2.0",
"json-loader": "^0.5.4",
@ -50,12 +52,15 @@
"npm-run-all": "^4.0.2",
"nyc": "^10.3.2",
"opn-cli": "^3.1.0",
"request": "^2.81.0",
"request-promise-native": "^1.0.4",
"shx": "^0.2.2",
"source-map-support": "^0.4.15",
"tslint": "^5.3.2",
"tslint-config-0xproject": "^0.0.2",
"typedoc": "^0.7.1",
"typescript": "^2.3.3",
"web3-provider-engine": "^12.1.0",
"web3-typescript-typings": "0.0.8",
"webpack": "^2.6.0"
},

5
src/ts/globals.d.ts vendored
View File

@ -1,5 +1,8 @@
declare module 'chai-bignumber';
declare module 'bn.js';
declare module 'request-promise-native';
declare module 'web3-provider-engine';
declare module 'web3-provider-engine/subproviders/rpc';
declare interface Schema {
id: string;
@ -12,6 +15,7 @@ declare interface Schema {
declare namespace Chai {
interface Assertion {
bignumber: Assertion;
eventually: Assertion;
}
}
/* tslint:enable */
@ -30,6 +34,7 @@ declare module 'ethereumjs-util' {
const ecrecover: (msgHashBuff: Buffer, v: number, r: Buffer, s: Buffer) => string;
const pubToAddress: (pubKey: string) => Buffer;
const isValidAddress: (address: string) => boolean;
const bufferToInt: (buffer: Buffer) => number;
}
// truffle-contract declarations

View File

@ -0,0 +1,99 @@
import 'mocha';
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
import {web3Factory} from './utils/web3_factory';
import * as Web3 from 'web3';
import {ExchangeWrapper} from '../src/ts/contract_wrappers/exchange_wrapper';
import {BlockchainClean} from './utils/blockchain_clean';
import {Web3Wrapper} from './../src/ts/web3_wrapper';
const expect = chai.expect;
chai.use(chaiAsPromised);
const blockchainClean = new BlockchainClean();
describe('ExchangeWrapper', () => {
let web3Wrapper: Web3Wrapper;
let exchangeWrapper: ExchangeWrapper;
before(async () => {
const web3 = web3Factory.create();
web3Wrapper = new Web3Wrapper(web3);
exchangeWrapper = new ExchangeWrapper(web3Wrapper);
});
beforeEach(async () => {
await blockchainClean.setupAsync();
});
afterEach(async () => {
await blockchainClean.restoreAsync();
});
describe('#isValidSignatureAsync', () => {
// The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
// the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const signature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
describe('should throw if passed a malformed signature', () => {
it('malformed v', async () => {
const malformedSignature = {
v: 34,
r: signature.r,
s: signature.s,
};
expect(exchangeWrapper.isValidSignatureAsync(dataHex, malformedSignature, address))
.to.be.rejected;
});
it('r lacks 0x prefix', () => {
const malformedR = signature.r.replace('0x', '');
const malformedSignature = {
v: signature.v,
r: malformedR,
s: signature.s,
};
expect(exchangeWrapper.isValidSignatureAsync(dataHex, malformedSignature, address))
.to.be.rejected;
});
it('r is too short', () => {
const malformedR = signature.r.substr(10);
const malformedSignature = {
v: signature.v,
r: malformedR,
s: signature.s.replace('0', 'z'),
};
expect(exchangeWrapper.isValidSignatureAsync(dataHex, malformedSignature, address))
.to.be.rejected;
});
it('s is not hex', () => {
const malformedS = signature.s.replace('0', 'z');
const malformedSignature = {
v: signature.v,
r: signature.r,
s: malformedS,
};
expect(exchangeWrapper.isValidSignatureAsync(dataHex, malformedSignature, address))
.to.be.rejected;
});
});
it('should return false if the data doesn\'t pertain to the signature & address', async () => {
const isValid = await exchangeWrapper.isValidSignatureAsync('0x0', signature, address);
expect(isValid).to.be.false;
});
it('should return false if the address doesn\'t pertain to the signature & dataHex', async () => {
const validUnrelatedAddress = '0x8b0292B11a196601eD2ce54B665CaFEca0347D42';
const isValid = await exchangeWrapper.isValidSignatureAsync(dataHex, signature, validUnrelatedAddress);
expect(isValid).to.be.false;
});
it('should return false if the signature doesn\'t pertain to the dataHex & address', async () => {
const wrongSignature = Object.assign({}, signature, {v: 28});
const isValid = await exchangeWrapper.isValidSignatureAsync(dataHex, wrongSignature, address);
expect(isValid).to.be.false;
});
it('should return true if the signature does pertain to the dataHex & address', async () => {
const isValid = await exchangeWrapper.isValidSignatureAsync(dataHex, signature, address);
console.log('isValid', isValid);
expect(isValid).to.be.true;
});
});
});

View File

@ -0,0 +1,19 @@
import {RPC} from './rpc';
export class BlockchainClean {
private rpc: RPC;
private snapshotId: number;
constructor() {
this.rpc = new RPC();
}
// TODO: Check if running on TestRPC or on actual node, if actual node, re-deploy contracts instead
public async setupAsync() {
this.snapshotId = await this.rpc.takeSnapshotAsync();
}
public async restoreAsync() {
const didRevert = await this.rpc.revertSnapshotAsync(this.snapshotId);
if (!didRevert) {
throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`);
}
}
};

5
test/utils/constants.ts Normal file
View File

@ -0,0 +1,5 @@
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
RPC_HOST: 'localhost',
RPC_PORT: 8545,
};

48
test/utils/rpc.ts Normal file
View File

@ -0,0 +1,48 @@
import * as ethUtil from 'ethereumjs-util';
import * as request from 'request-promise-native';
import {constants} from './constants';
export class RPC {
private host: string;
private port: number;
private id: number;
constructor() {
this.host = constants.RPC_HOST;
this.port = constants.RPC_PORT;
this.id = 0;
}
public async takeSnapshotAsync(): Promise<number> {
const method = 'evm_snapshot';
const params: any[] = [];
const payload = this.toPayload(method, params);
const snapshotIdHex = await this.sendAsync(payload);
const snapshotId = ethUtil.bufferToInt(ethUtil.toBuffer(snapshotIdHex));
return snapshotId;
}
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
const method = 'evm_revert';
const params = [snapshotId];
const payload = this.toPayload(method, params);
const didRevert = await this.sendAsync(payload);
return didRevert;
}
private toPayload(method: string, params: any[] = []) {
const payload = JSON.stringify({
id: this.id,
method,
params,
});
this.id += 1;
return payload;
}
private async sendAsync(payload: string) {
const opts = {
method: 'POST',
uri: `http://${this.host}:${this.port}`,
body: payload,
};
const bodyString = await request(opts);
const body = JSON.parse(bodyString);
return body.result;
}
}

View File

@ -0,0 +1,23 @@
// HACK: web3 injects XMLHttpRequest into the global scope and ProviderEngine checks XMLHttpRequest
// to know whether it is running in a browser or node environment. We need it to be undefined since
// we are not running in a browser env.
// Filed issue: https://github.com/ethereum/web3.js/issues/844
(global as any).XMLHttpRequest = undefined;
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import * as Web3 from 'web3';
import {constants} from './constants';
export const web3Factory = {
create(): Web3 {
const provider = new ProviderEngine();
const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`;
provider.addProvider(new RpcSubprovider({
rpcUrl,
}));
provider.start();
const web3 = new Web3();
web3.setProvider(provider);
return web3;
},
};