Add more assertions to Web3Wrapper public methods

This commit is contained in:
Fabio Berger 2018-07-04 08:54:43 +02:00
parent 590033bcb2
commit cd766ea2a1
2 changed files with 104 additions and 3 deletions

View File

@ -1,6 +1,8 @@
import { assert } from '@0xproject/assert';
import { AbiDecoder, addressUtils, BigNumber, intervalUtils, promisify } from '@0xproject/utils'; import { AbiDecoder, addressUtils, BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import { import {
BlockParam, BlockParam,
BlockParamLiteral,
BlockWithoutTransactionData, BlockWithoutTransactionData,
BlockWithTransactionData, BlockWithTransactionData,
CallData, CallData,
@ -60,6 +62,8 @@ export class Web3Wrapper {
* @return The amount in units. * @return The amount in units.
*/ */
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isValidBaseUnitAmount('amount', amount);
assert.isNumber('decimals', decimals);
const aUnit = new BigNumber(BASE_TEN).pow(decimals); const aUnit = new BigNumber(BASE_TEN).pow(decimals);
const unit = amount.div(aUnit); const unit = amount.div(aUnit);
return unit; return unit;
@ -73,6 +77,8 @@ export class Web3Wrapper {
* @return The amount in baseUnits. * @return The amount in baseUnits.
*/ */
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isBigNumber('amount', amount);
assert.isNumber('decimals', decimals);
const unit = new BigNumber(BASE_TEN).pow(decimals); const unit = new BigNumber(BASE_TEN).pow(decimals);
const baseUnitAmount = amount.times(unit); const baseUnitAmount = amount.times(unit);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
@ -87,10 +93,30 @@ export class Web3Wrapper {
* @returns Amount in wei * @returns Amount in wei
*/ */
public static toWei(ethAmount: BigNumber): BigNumber { public static toWei(ethAmount: BigNumber): BigNumber {
assert.isBigNumber('ethAmount', ethAmount);
const ETH_DECIMALS = 18; const ETH_DECIMALS = 18;
const balanceWei = Web3Wrapper.toBaseUnitAmount(ethAmount, ETH_DECIMALS); const balanceWei = Web3Wrapper.toBaseUnitAmount(ethAmount, ETH_DECIMALS);
return balanceWei; return balanceWei;
} }
private static _assertBlockParam(blockParam: string | BlockParam): void {
if (_.isNumber(blockParam)) {
return;
} else if (_.isString(blockParam)) {
assert.doesBelongToStringEnum('blockParam', blockParam, BlockParamLiteral);
}
}
private static _assertBlockParamOrString(blockParam: string | BlockParam): void {
try {
Web3Wrapper._assertBlockParam(blockParam);
} catch (err) {
try {
assert.isHexString('blockParam', blockParam as string);
return;
} catch (err) {
throw new Error(`Expected blockParam to be of type "string | BlockParam", encountered ${blockParam}`);
}
}
}
/** /**
* Instantiates a new Web3Wrapper. * Instantiates a new Web3Wrapper.
* @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with * @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with
@ -99,6 +125,7 @@ export class Web3Wrapper {
* @return An instance of the Web3Wrapper class. * @return An instance of the Web3Wrapper class.
*/ */
constructor(provider: Provider, txDefaults?: Partial<TxData>) { constructor(provider: Provider, txDefaults?: Partial<TxData>) {
assert.isWeb3Provider('provider', provider);
if (_.isUndefined((provider as any).sendAsync)) { if (_.isUndefined((provider as any).sendAsync)) {
// Web3@1.0 provider doesn't support synchronous http requests, // Web3@1.0 provider doesn't support synchronous http requests,
// so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
@ -130,6 +157,7 @@ export class Web3Wrapper {
* @param provider The new Web3 provider to be set * @param provider The new Web3 provider to be set
*/ */
public setProvider(provider: Provider): void { public setProvider(provider: Provider): void {
assert.isWeb3Provider('provider', provider);
this._web3.setProvider(provider); this._web3.setProvider(provider);
} }
/** /**
@ -140,6 +168,7 @@ export class Web3Wrapper {
* @returns Whether the address is available through the provider. * @returns Whether the address is available through the provider.
*/ */
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
assert.isETHAddressHex('senderAddress', senderAddress);
const addresses = await this.getAvailableAddressesAsync(); const addresses = await this.getAvailableAddressesAsync();
const normalizedAddress = senderAddress.toLowerCase(); const normalizedAddress = senderAddress.toLowerCase();
return _.includes(addresses, normalizedAddress); return _.includes(addresses, normalizedAddress);
@ -179,10 +208,13 @@ export class Web3Wrapper {
* @returns Balance in wei * @returns Balance in wei
*/ */
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); assert.isETHAddressHex('owner', owner);
const balanceInWei = await this._sendRawPayloadAsync<string>({
method: 'eth_getBalance',
params: [owner],
});
// Rewrap in a new BigNumber // Rewrap in a new BigNumber
balanceInWei = new BigNumber(balanceInWei); return new BigNumber(balanceInWei);
return balanceInWei;
} }
/** /**
* Check if a contract exists at a given address * Check if a contract exists at a given address
@ -190,6 +222,7 @@ export class Web3Wrapper {
* @returns Whether or not contract code was found at the supplied address * @returns Whether or not contract code was found at the supplied address
*/ */
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
assert.isETHAddressHex('address', address);
const code = await this.getContractCodeAsync(address); const code = await this.getContractCodeAsync(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const isCodeEmpty = /^0x0{0,40}$/i.test(code); const isCodeEmpty = /^0x0{0,40}$/i.test(code);
@ -201,6 +234,7 @@ export class Web3Wrapper {
* @return Code of the contract * @return Code of the contract
*/ */
public async getContractCodeAsync(address: string): Promise<string> { public async getContractCodeAsync(address: string): Promise<string> {
assert.isETHAddressHex('address', address);
const code = await promisify<string>(this._web3.eth.getCode)(address); const code = await promisify<string>(this._web3.eth.getCode)(address);
return code; return code;
} }
@ -211,6 +245,7 @@ export class Web3Wrapper {
* @return Transaction trace * @return Transaction trace
*/ */
public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> { public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> {
assert.isHexString('txHash', txHash);
const trace = await this._sendRawPayloadAsync<TransactionTrace>({ const trace = await this._sendRawPayloadAsync<TransactionTrace>({
method: 'debug_traceTransaction', method: 'debug_traceTransaction',
params: [txHash, traceParams], params: [txHash, traceParams],
@ -224,6 +259,8 @@ export class Web3Wrapper {
* @returns Signature string (might be VRS or RSV depending on the Signer) * @returns Signature string (might be VRS or RSV depending on the Signer)
*/ */
public async signMessageAsync(address: string, message: string): Promise<string> { public async signMessageAsync(address: string, message: string): Promise<string> {
assert.isETHAddressHex('address', address);
assert.isString('message', message); // TODO: Should this be stricter? Hex string?
const signData = await promisify<string>(this._web3.eth.sign)(address, message); const signData = await promisify<string>(this._web3.eth.sign)(address, message);
return signData; return signData;
} }
@ -241,6 +278,7 @@ export class Web3Wrapper {
* @returns The requested block without transaction data * @returns The requested block without transaction data
*/ */
public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> { public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> {
Web3Wrapper._assertBlockParamOrString(blockParam);
const shouldIncludeTransactionData = false; const shouldIncludeTransactionData = false;
const blockWithoutTransactionData = await promisify<BlockWithoutTransactionData>(this._web3.eth.getBlock)( const blockWithoutTransactionData = await promisify<BlockWithoutTransactionData>(this._web3.eth.getBlock)(
blockParam, blockParam,
@ -254,6 +292,7 @@ export class Web3Wrapper {
* @returns The requested block with transaction data * @returns The requested block with transaction data
*/ */
public async getBlockWithTransactionDataAsync(blockParam: string | BlockParam): Promise<BlockWithTransactionData> { public async getBlockWithTransactionDataAsync(blockParam: string | BlockParam): Promise<BlockWithTransactionData> {
Web3Wrapper._assertBlockParamOrString(blockParam);
const shouldIncludeTransactionData = true; const shouldIncludeTransactionData = true;
const blockWithTransactionData = await promisify<BlockWithTransactionData>(this._web3.eth.getBlock)( const blockWithTransactionData = await promisify<BlockWithTransactionData>(this._web3.eth.getBlock)(
blockParam, blockParam,
@ -267,6 +306,7 @@ export class Web3Wrapper {
* @returns The block's timestamp * @returns The block's timestamp
*/ */
public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> { public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> {
Web3Wrapper._assertBlockParamOrString(blockParam);
const { timestamp } = await this.getBlockAsync(blockParam); const { timestamp } = await this.getBlockAsync(blockParam);
return timestamp; return timestamp;
} }
@ -293,6 +333,7 @@ export class Web3Wrapper {
* @returns Whether the revert was successful * @returns Whether the revert was successful
*/ */
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
assert.isNumber('snapshotId', snapshotId);
const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] }); const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] });
return didRevert; return didRevert;
} }
@ -308,6 +349,7 @@ export class Web3Wrapper {
* @param timeDelta Amount of time to add in seconds * @param timeDelta Amount of time to add in seconds
*/ */
public async increaseTimeAsync(timeDelta: number): Promise<number> { public async increaseTimeAsync(timeDelta: number): Promise<number> {
assert.isNumber('timeDelta', timeDelta);
// Detect Geth vs. Ganache and use appropriate endpoint. // Detect Geth vs. Ganache and use appropriate endpoint.
const version = await this.getNodeVersionAsync(); const version = await this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) { if (_.includes(version, uniqueVersionIds.geth)) {
@ -371,6 +413,9 @@ export class Web3Wrapper {
* @returns The raw call result * @returns The raw call result
*/ */
public async callAsync(callData: CallData, defaultBlock?: BlockParam): Promise<string> { public async callAsync(callData: CallData, defaultBlock?: BlockParam): Promise<string> {
if (!_.isUndefined(defaultBlock)) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock);
if (rawCallResult === '0x') { if (rawCallResult === '0x') {
throw new Error('Contract call failed (returned null)'); throw new Error('Contract call failed (returned null)');
@ -402,6 +447,11 @@ export class Web3Wrapper {
pollingIntervalMs: number = 1000, pollingIntervalMs: number = 1000,
timeoutMs?: number, timeoutMs?: number,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
assert.isHexString('txHash', txHash);
assert.isNumber('pollingIntervalMs', pollingIntervalMs);
if (!_.isUndefined(timeoutMs)) {
assert.isNumber('timeoutMs', timeoutMs);
}
// Immediately check if the transaction has already been mined. // Immediately check if the transaction has already been mined.
let transactionReceipt = await this.getTransactionReceiptAsync(txHash); let transactionReceipt = await this.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) { if (!_.isNull(transactionReceipt)) {
@ -487,6 +537,7 @@ export class Web3Wrapper {
* @param blockNumber The block number to reset to. * @param blockNumber The block number to reset to.
*/ */
public async setHeadAsync(blockNumber: number): Promise<void> { public async setHeadAsync(blockNumber: number): Promise<void> {
assert.isNumber('blockNumber', blockNumber);
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [this._web3.toHex(blockNumber)] }); await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [this._web3.toHex(blockNumber)] });
} }
private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> { private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {

View File

@ -1,4 +1,5 @@
import * as chai from 'chai'; import * as chai from 'chai';
import { BlockParamLiteral } from 'ethereum-types';
import * as Ganache from 'ganache-core'; import * as Ganache from 'ganache-core';
import 'mocha'; import 'mocha';
@ -9,6 +10,8 @@ chaiSetup.configure();
const { expect } = chai; const { expect } = chai;
const NUM_GANACHE_ADDRESSES = 10;
describe('Web3Wrapper tests', () => { describe('Web3Wrapper tests', () => {
const NETWORK_ID = 50; const NETWORK_ID = 50;
const provider = Ganache.provider({ network_id: NETWORK_ID }); const provider = Ganache.provider({ network_id: NETWORK_ID });
@ -36,4 +39,51 @@ describe('Web3Wrapper tests', () => {
expect(networkId).to.be.equal(NETWORK_ID); expect(networkId).to.be.equal(NETWORK_ID);
}); });
}); });
describe('#getNetworkIdAsync', () => {
it('gets the network id', async () => {
const networkId = await web3Wrapper.getNetworkIdAsync();
expect(networkId).to.be.equal(NETWORK_ID);
});
});
describe('#getAvailableAddressesAsync', () => {
it('gets the available addresses', async () => {
const addresses = await web3Wrapper.getAvailableAddressesAsync();
expect(addresses.length).to.be.equal(NUM_GANACHE_ADDRESSES);
});
});
describe('#getBalanceInWeiAsync', () => {
it('gets the users balance in wei', async () => {
const addresses = await web3Wrapper.getAvailableAddressesAsync();
const secondAccount = addresses[1];
const balanceInWei = await web3Wrapper.getBalanceInWeiAsync(secondAccount);
const tenEthInWei = 100000000000000000000;
expect(balanceInWei).to.be.bignumber.equal(tenEthInWei);
});
it('should throw if supplied owner not an Ethereum address hex string', async () => {
const invalidEthAddress = 'deadbeef';
expect(web3Wrapper.getBalanceInWeiAsync(invalidEthAddress)).to.eventually.to.be.rejected();
});
});
describe('#getBlockAsync', () => {
it('gets block when supplied a valid BlockParamLiteral value', async () => {
const blockParamLiteral = BlockParamLiteral.Earliest;
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
expect(block.number).to.be.equal(0);
});
it('gets block when supplied a block number', async () => {
const blockParamLiteral = 0;
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
expect(block.number).to.be.equal(0);
});
it('gets block when supplied a block hash', async () => {
const blockParamLiteral = 0;
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
const sameBlock = await web3Wrapper.getBlockAsync(block.hash as string);
expect(sameBlock.number).to.be.equal(0);
});
it('should throw if supplied invalid blockParam value', async () => {
const invalidBlockParam = 'deadbeef';
expect(web3Wrapper.getBlockAsync(invalidBlockParam)).to.eventually.to.be.rejected();
});
});
}); });