Merge pull request #821 from 0xProject/remove-web3-from-wrapper

Remove Web3.js From 0x-monorepo 🍾
This commit is contained in:
Fabio Berger
2018-07-05 15:04:58 +02:00
committed by GitHub
13 changed files with 575 additions and 92 deletions

View File

@@ -1,4 +1,14 @@
[
{
"version": "0.2.13",
"changes": [
{
"note":
"Fix bug in string enum assertion. We erroneously were checking against the enum keys, not values",
"pr": 821
}
]
},
{
"timestamp": 1529397769,
"version": "0.2.12",

View File

@@ -41,8 +41,8 @@ export const assert = {
value: string,
stringEnum: any /* There is no base type for every string enum */,
): void {
const doesBelongToStringEnum = !_.isUndefined(stringEnum[value]);
const enumValues = _.keys(stringEnum);
const enumValues = _.values(stringEnum);
const doesBelongToStringEnum = _.includes(enumValues, value);
const enumValuesAsStrings = _.map(enumValues, enumValue => `'${enumValue}'`);
const enumValuesAsString = enumValuesAsStrings.join(', ');
assert.assert(

View File

@@ -80,7 +80,7 @@ export class BaseContract {
// Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged
} as any;
if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) {
txDataWithDefaults.gas = await estimateGasAsync(txData);
txDataWithDefaults.gas = await estimateGasAsync(txDataWithDefaults as any);
}
return txDataWithDefaults;
}

View File

@@ -5,6 +5,10 @@
{
"note": "Update schemas for V2",
"pr": 615
},
{
"note": "Added CallData schema",
"pr": 821
}
]
},

View File

@@ -0,0 +1,27 @@
export const callDataSchema = {
id: '/TxData',
properties: {
from: { $ref: '/Address' },
to: { $ref: '/Address' },
value: {
oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }],
},
gas: {
oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }],
},
gasPrice: {
oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }],
},
data: {
type: 'string',
pattern: '^0x[0-9a-f]*$',
},
nonce: {
type: 'number',
minimum: 0,
},
},
required: [],
type: 'object',
additionalProperties: false,
};

View File

@@ -1,5 +1,6 @@
import { addressSchema, hexSchema, numberSchema } from '../schemas/basic_type_schemas';
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
import { callDataSchema } from '../schemas/call_data_schema';
import { ecSignatureSchema } from '../schemas/ec_signature_schema';
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
@@ -31,6 +32,7 @@ import { jsNumber, txDataSchema } from '../schemas/tx_data_schema';
export const schemas = {
numberSchema,
addressSchema,
callDataSchema,
hexSchema,
ecSignatureSchema,
indexFilterValuesSchema,

View File

@@ -64,12 +64,14 @@
"typescript": "2.7.1"
},
"dependencies": {
"@0xproject/assert": "^0.2.12",
"@0xproject/json-schemas": "^1.0.0",
"@0xproject/typescript-typings": "^0.4.1",
"@0xproject/utils": "^0.7.1",
"ethereum-types": "^0.0.2",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",
"lodash": "^4.17.4",
"web3": "^0.20.0"
"lodash": "^4.17.4"
},
"publishConfig": {
"access": "public"

View File

@@ -0,0 +1,149 @@
import { addressUtils } from '@0xproject/utils';
import {
BlockParam,
BlockParamLiteral,
BlockWithoutTransactionData,
BlockWithTransactionData,
CallData,
CallTxDataBase,
LogEntry,
RawLogEntry,
Transaction,
TxData,
} from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import { utils } from './utils';
import {
BlockWithoutTransactionDataRPC,
BlockWithTransactionDataRPC,
CallDataRPC,
CallTxDataBaseRPC,
TransactionRPC,
TxDataRPC,
} from './types';
export const marshaller = {
unmarshalIntoBlockWithoutTransactionData(
blockWithHexValues: BlockWithoutTransactionDataRPC,
): BlockWithoutTransactionData {
const block = {
...blockWithHexValues,
gasLimit: utils.convertHexToNumber(blockWithHexValues.gasLimit),
gasUsed: utils.convertHexToNumber(blockWithHexValues.gasUsed),
size: utils.convertHexToNumber(blockWithHexValues.size),
timestamp: utils.convertHexToNumber(blockWithHexValues.timestamp),
number: _.isNull(blockWithHexValues.number) ? null : utils.convertHexToNumber(blockWithHexValues.number),
difficulty: utils.convertAmountToBigNumber(blockWithHexValues.difficulty),
totalDifficulty: utils.convertAmountToBigNumber(blockWithHexValues.totalDifficulty),
};
return block;
},
unmarshalIntoBlockWithTransactionData(blockWithHexValues: BlockWithTransactionDataRPC): BlockWithTransactionData {
const block = {
...blockWithHexValues,
gasLimit: utils.convertHexToNumber(blockWithHexValues.gasLimit),
gasUsed: utils.convertHexToNumber(blockWithHexValues.gasUsed),
size: utils.convertHexToNumber(blockWithHexValues.size),
timestamp: utils.convertHexToNumber(blockWithHexValues.timestamp),
number: _.isNull(blockWithHexValues.number) ? null : utils.convertHexToNumber(blockWithHexValues.number),
difficulty: utils.convertAmountToBigNumber(blockWithHexValues.difficulty),
totalDifficulty: utils.convertAmountToBigNumber(blockWithHexValues.totalDifficulty),
transactions: [] as Transaction[],
};
block.transactions = _.map(blockWithHexValues.transactions, (tx: TransactionRPC) => {
const transaction = this.unmarshalTransaction(tx);
return transaction;
});
return block;
},
unmarshalTransaction(txRpc: TransactionRPC): Transaction {
const tx = {
...txRpc,
blockNumber: !_.isNull(txRpc.blockNumber) ? utils.convertHexToNumber(txRpc.blockNumber) : null,
transactionIndex: !_.isNull(txRpc.transactionIndex)
? utils.convertHexToNumber(txRpc.transactionIndex)
: null,
nonce: utils.convertHexToNumber(txRpc.nonce),
gas: utils.convertHexToNumber(txRpc.gas),
gasPrice: utils.convertAmountToBigNumber(txRpc.gasPrice),
value: utils.convertAmountToBigNumber(txRpc.value),
};
return tx;
},
marshalTxData(txData: Partial<TxData>): Partial<TxDataRPC> {
if (_.isUndefined(txData.from)) {
throw new Error(`txData must include valid 'from' value.`);
}
const callTxDataBase = {
...txData,
};
delete callTxDataBase.from;
const callTxDataBaseRPC = this._marshalCallTxDataBase(callTxDataBase);
const txDataRPC = {
...callTxDataBaseRPC,
from: this.marshalAddress(txData.from),
};
const prunableIfUndefined = ['gasPrice', 'gas', 'value', 'nonce'];
_.each(txDataRPC, (value: any, key: string) => {
if (_.isUndefined(value) && _.includes(prunableIfUndefined, key)) {
delete (txDataRPC as any)[key];
}
});
return txDataRPC;
},
marshalCallData(callData: Partial<CallData>): Partial<CallDataRPC> {
const callTxDataBase = {
...callData,
};
delete callTxDataBase.from;
const callTxDataBaseRPC = this._marshalCallTxDataBase(callTxDataBase);
const callDataRPC = {
...callTxDataBaseRPC,
from: _.isUndefined(callData.from) ? undefined : this.marshalAddress(callData.from),
};
return callDataRPC;
},
marshalAddress(address: string): string {
if (addressUtils.isAddress(address)) {
return ethUtil.addHexPrefix(address);
}
throw new Error(`Invalid address encountered: ${address}`);
},
marshalBlockParam(blockParam: BlockParam | string | number | undefined): string | undefined {
if (_.isUndefined(blockParam)) {
return BlockParamLiteral.Latest;
}
const encodedBlockParam = _.isNumber(blockParam) ? utils.numberToHex(blockParam) : blockParam;
return encodedBlockParam;
},
unmarshalLog(rawLog: RawLogEntry): LogEntry {
const formattedLog = {
...rawLog,
logIndex: utils.convertHexToNumberOrNull(rawLog.logIndex),
blockNumber: utils.convertHexToNumberOrNull(rawLog.blockNumber),
transactionIndex: utils.convertHexToNumberOrNull(rawLog.transactionIndex),
};
return formattedLog;
},
_marshalCallTxDataBase(callTxDataBase: Partial<CallTxDataBase>): Partial<CallTxDataBaseRPC> {
const callTxDataBaseRPC = {
...callTxDataBase,
to: _.isUndefined(callTxDataBase.to) ? undefined : this.marshalAddress(callTxDataBase.to),
gasPrice: _.isUndefined(callTxDataBase.gasPrice)
? undefined
: utils.encodeAmountAsHexString(callTxDataBase.gasPrice),
gas: _.isUndefined(callTxDataBase.gas) ? undefined : utils.encodeAmountAsHexString(callTxDataBase.gas),
value: _.isUndefined(callTxDataBase.value)
? undefined
: utils.encodeAmountAsHexString(callTxDataBase.value),
nonce: _.isUndefined(callTxDataBase.nonce)
? undefined
: utils.encodeAmountAsHexString(callTxDataBase.nonce),
};
return callTxDataBaseRPC;
},
};

View File

@@ -1,3 +1,59 @@
export enum Web3WrapperErrors {
TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
}
export interface AbstractBlockRPC {
number: string | null;
hash: string | null;
parentHash: string;
nonce: string | null;
sha3Uncles: string;
logsBloom: string | null;
transactionsRoot: string;
stateRoot: string;
miner: string;
difficulty: string;
totalDifficulty: string;
extraData: string;
size: string;
gasLimit: string;
gasUsed: string;
timestamp: string;
uncles: string[];
}
export interface BlockWithoutTransactionDataRPC extends AbstractBlockRPC {
transactions: string[];
}
export interface BlockWithTransactionDataRPC extends AbstractBlockRPC {
transactions: TransactionRPC[];
}
export interface TransactionRPC {
hash: string;
nonce: string;
blockHash: string | null;
blockNumber: string | null;
transactionIndex: string | null;
from: string;
to: string | null;
value: string;
gasPrice: string;
gas: string;
input: string;
}
export interface CallTxDataBaseRPC {
to?: string;
value?: string;
gas?: string;
gasPrice?: string;
data?: string;
nonce?: string;
}
export interface TxDataRPC extends CallTxDataBaseRPC {
from: string;
}
export interface CallDataRPC extends CallTxDataBaseRPC {
from?: string;
}

View File

@@ -0,0 +1,58 @@
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
export const utils = {
isBigNumber(value: any): boolean {
const isBigNumber = _.isObject(value) && value.isBigNumber;
return isBigNumber;
},
convertHexToNumber(value: string): number {
const valueBigNumber = new BigNumber(value);
const valueNumber = valueBigNumber.toNumber();
return valueNumber;
},
convertHexToNumberOrNull(hex: string | null): number | null {
if (_.isNull(hex)) {
return null;
}
const decimal = this.convertHexToNumber(hex);
return decimal;
},
convertAmountToBigNumber(value: string | number | BigNumber): BigNumber {
const num = value || 0;
const isBigNumber = utils.isBigNumber(num);
if (isBigNumber) {
return num as BigNumber;
}
if (_.isString(num) && (num.indexOf('0x') === 0 || num.indexOf('-0x') === 0)) {
return new BigNumber(num.replace('0x', ''), 16);
}
const baseTen = 10;
return new BigNumber((num as number).toString(baseTen), baseTen);
},
encodeAmountAsHexString(value: string | number | BigNumber): string {
const valueBigNumber = utils.convertAmountToBigNumber(value);
const hexBase = 16;
const valueHex = valueBigNumber.toString(hexBase);
return valueBigNumber.lessThan(0) ? '-0x' + valueHex.substr(1) : '0x' + valueHex;
},
numberToHex(value: number): string {
if (!isFinite(value) && !this.isHexStrict(value)) {
throw new Error(`Given input ${value} is not a number.`);
}
const valueBigNumber = new BigNumber(value);
const hexBase = 16;
const result = valueBigNumber.toString(hexBase);
return valueBigNumber.lt(0) ? '-0x' + result.substr(1) : '0x' + result;
},
isHexStrict(hex: string | number): boolean {
return (
(_.isString(hex) || _.isNumber(hex)) && /^(-)?0x[0-9a-f]*$/i.test(_.isNumber(hex) ? hex.toString() : hex)
);
},
};

View File

@@ -1,10 +1,12 @@
import { assert } from '@0xproject/assert';
import { schemas } from '@0xproject/json-schemas';
import { AbiDecoder, addressUtils, BigNumber, intervalUtils, promisify } from '@0xproject/utils';
import {
BlockParam,
BlockParamLiteral,
BlockWithoutTransactionData,
BlockWithTransactionData,
CallData,
ContractAbi,
FilterObject,
JSONRPCRequestPayload,
JSONRPCResponsePayload,
@@ -18,9 +20,10 @@ import {
TxData,
} from 'ethereum-types';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import { Web3WrapperErrors } from './types';
import { marshaller } from './marshaller';
import { BlockWithoutTransactionDataRPC, BlockWithTransactionDataRPC, Web3WrapperErrors } from './types';
import { utils } from './utils';
const BASE_TEN = 10;
@@ -38,7 +41,7 @@ export enum NodeType {
}
/**
* A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface.
* An alternative to the Web3.js library that provides a consistent, clean, promise-based interface.
*/
export class Web3Wrapper {
/**
@@ -46,7 +49,7 @@ export class Web3Wrapper {
*/
public isZeroExWeb3Wrapper = true;
public abiDecoder: AbiDecoder;
private _web3: Web3;
private _provider: Provider;
private _txDefaults: Partial<TxData>;
private _jsonRpcRequestId: number;
/**
@@ -66,6 +69,8 @@ export class Web3Wrapper {
* @return The amount in units.
*/
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 unit = amount.div(aUnit);
return unit;
@@ -79,6 +84,8 @@ export class Web3Wrapper {
* @return The amount in baseUnits.
*/
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 baseUnitAmount = amount.times(unit);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
@@ -93,10 +100,44 @@ export class Web3Wrapper {
* @returns Amount in wei
*/
public static toWei(ethAmount: BigNumber): BigNumber {
assert.isBigNumber('ethAmount', ethAmount);
const ETH_DECIMALS = 18;
const balanceWei = Web3Wrapper.toBaseUnitAmount(ethAmount, ETH_DECIMALS);
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}`);
}
}
}
private static _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 {
// Transaction status might have four values
// undefined - Testrpc and other old clients
// null - New clients on old transactions
// number - Parity
// hex - Geth
if (_.isString(status)) {
return utils.convertHexToNumber(status) as 0 | 1;
} else if (_.isUndefined(status)) {
return null;
} else {
return status;
}
}
/**
* Instantiates a new Web3Wrapper.
* @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with
@@ -105,6 +146,7 @@ export class Web3Wrapper {
* @return An instance of the Web3Wrapper class.
*/
constructor(provider: Provider, txDefaults?: Partial<TxData>) {
assert.isWeb3Provider('provider', provider);
if (_.isUndefined((provider as any).sendAsync)) {
// 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`
@@ -112,8 +154,7 @@ export class Web3Wrapper {
(provider as any).sendAsync = (provider as any).send;
}
this.abiDecoder = new AbiDecoder([]);
this._web3 = new Web3();
this._web3.setProvider(provider);
this._provider = provider;
this._txDefaults = txDefaults || {};
this._jsonRpcRequestId = 0;
}
@@ -129,14 +170,15 @@ export class Web3Wrapper {
* @return Web3 provider instance
*/
public getProvider(): Provider {
return this._web3.currentProvider;
return this._provider;
}
/**
* Update the used Web3 provider
* @param provider The new Web3 provider to be set
*/
public setProvider(provider: Provider): void {
this._web3.setProvider(provider);
assert.isWeb3Provider('provider', provider);
this._provider = provider;
}
/**
* Check whether an address is available through the backing provider. This can be
@@ -146,6 +188,7 @@ export class Web3Wrapper {
* @returns Whether the address is available through the provider.
*/
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
assert.isETHAddressHex('senderAddress', senderAddress);
const addresses = await this.getAvailableAddressesAsync();
const normalizedAddress = senderAddress.toLowerCase();
return _.includes(addresses, normalizedAddress);
@@ -173,9 +216,13 @@ export class Web3Wrapper {
* @returns The transaction receipt, including it's status (0: failed, 1: succeeded or undefined: not found)
*/
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash);
assert.isHexString('txHash', txHash);
const transactionReceipt = await this._sendRawPayloadAsync<TransactionReceipt>({
method: 'eth_getTransactionReceipt',
params: [txHash],
});
if (!_.isNull(transactionReceipt)) {
transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status);
transactionReceipt.status = Web3Wrapper._normalizeTxReceiptStatus(transactionReceipt.status);
}
return transactionReceipt;
}
@@ -184,11 +231,19 @@ export class Web3Wrapper {
* @param owner Account whose balance you wish to check
* @returns Balance in wei
*/
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner);
public async getBalanceInWeiAsync(owner: string, defaultBlock?: BlockParam): Promise<BigNumber> {
assert.isETHAddressHex('owner', owner);
if (!_.isUndefined(defaultBlock)) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedOwner = marshaller.marshalAddress(owner);
const balanceInWei = await this._sendRawPayloadAsync<string>({
method: 'eth_getBalance',
params: [encodedOwner, marshalledDefaultBlock],
});
// Rewrap in a new BigNumber
balanceInWei = new BigNumber(balanceInWei);
return balanceInWei;
return new BigNumber(balanceInWei);
}
/**
* Check if a contract exists at a given address
@@ -196,6 +251,7 @@ export class Web3Wrapper {
* @returns Whether or not contract code was found at the supplied address
*/
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
assert.isETHAddressHex('address', address);
const code = await this.getContractCodeAsync(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const isCodeEmpty = /^0x0{0,40}$/i.test(code);
@@ -204,10 +260,20 @@ export class Web3Wrapper {
/**
* Gets the contract code by address
* @param address Address of the contract
* @param defaultBlock Block height at which to make the call. Defaults to `latest`
* @return Code of the contract
*/
public async getContractCodeAsync(address: string): Promise<string> {
const code = await promisify<string>(this._web3.eth.getCode)(address);
public async getContractCodeAsync(address: string, defaultBlock?: BlockParam): Promise<string> {
assert.isETHAddressHex('address', address);
if (!_.isUndefined(defaultBlock)) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedAddress = marshaller.marshalAddress(address);
const code = await this._sendRawPayloadAsync<string>({
method: 'eth_getCode',
params: [encodedAddress, marshalledDefaultBlock],
});
return code;
}
/**
@@ -217,6 +283,7 @@ export class Web3Wrapper {
* @return Transaction trace
*/
public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> {
assert.isHexString('txHash', txHash);
const trace = await this._sendRawPayloadAsync<TransactionTrace>({
method: 'debug_traceTransaction',
params: [txHash, traceParams],
@@ -230,7 +297,13 @@ export class Web3Wrapper {
* @returns Signature string (might be VRS or RSV depending on the Signer)
*/
public async signMessageAsync(address: string, message: string): Promise<string> {
const signData = await promisify<string>(this._web3.eth.sign)(address, message);
assert.isETHAddressHex('address', address);
assert.isETHAddressHex('address', address);
assert.isString('message', message); // TODO: Should this be stricter? Hex string?
const signData = await this._sendRawPayloadAsync<string>({
method: 'eth_sign',
params: [address, message],
});
return signData;
}
/**
@@ -238,8 +311,12 @@ export class Web3Wrapper {
* @returns Block number
*/
public async getBlockNumberAsync(): Promise<number> {
const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)();
return blockNumber;
const blockNumberHex = await this._sendRawPayloadAsync<string>({
method: 'eth_blockNumber',
params: [],
});
const blockNumber = utils.convertHexToNumberOrNull(blockNumberHex);
return blockNumber as number;
}
/**
* Fetch a specific Ethereum block without transaction data
@@ -247,10 +324,18 @@ export class Web3Wrapper {
* @returns The requested block without transaction data
*/
public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> {
Web3Wrapper._assertBlockParamOrString(blockParam);
const encodedBlockParam = marshaller.marshalBlockParam(blockParam);
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = false;
const blockWithoutTransactionData = await promisify<BlockWithoutTransactionData>(this._web3.eth.getBlock)(
blockParam,
shouldIncludeTransactionData,
const blockWithoutTransactionDataWithHexValues = await this._sendRawPayloadAsync<
BlockWithoutTransactionDataRPC
>({
method,
params: [encodedBlockParam, shouldIncludeTransactionData],
});
const blockWithoutTransactionData = marshaller.unmarshalIntoBlockWithoutTransactionData(
blockWithoutTransactionDataWithHexValues,
);
return blockWithoutTransactionData;
}
@@ -260,12 +345,21 @@ export class Web3Wrapper {
* @returns The requested block with transaction data
*/
public async getBlockWithTransactionDataAsync(blockParam: string | BlockParam): Promise<BlockWithTransactionData> {
Web3Wrapper._assertBlockParamOrString(blockParam);
let encodedBlockParam = blockParam;
if (_.isNumber(blockParam)) {
encodedBlockParam = utils.numberToHex(blockParam);
}
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = true;
const blockWithTransactionData = await promisify<BlockWithTransactionData>(this._web3.eth.getBlock)(
blockParam,
shouldIncludeTransactionData,
const blockWithTransactionDataWithHexValues = await this._sendRawPayloadAsync<BlockWithTransactionDataRPC>({
method,
params: [encodedBlockParam, shouldIncludeTransactionData],
});
const blockWithoutTransactionData = marshaller.unmarshalIntoBlockWithTransactionData(
blockWithTransactionDataWithHexValues,
);
return blockWithTransactionData;
return blockWithoutTransactionData;
}
/**
* Fetch a block's timestamp
@@ -273,6 +367,7 @@ export class Web3Wrapper {
* @returns The block's timestamp
*/
public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> {
Web3Wrapper._assertBlockParamOrString(blockParam);
const { timestamp } = await this.getBlockAsync(blockParam);
return timestamp;
}
@@ -281,7 +376,10 @@ export class Web3Wrapper {
* @returns Available user addresses
*/
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses = await promisify<string[]>(this._web3.eth.getAccounts)();
const addresses = await this._sendRawPayloadAsync<string>({
method: 'eth_accounts',
params: [],
});
const normalizedAddresses = _.map(addresses, address => address.toLowerCase());
return normalizedAddresses;
}
@@ -299,6 +397,7 @@ export class Web3Wrapper {
* @returns Whether the revert was successful
*/
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
assert.isNumber('snapshotId', snapshotId);
const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] });
return didRevert;
}
@@ -314,6 +413,7 @@ export class Web3Wrapper {
* @param timeDelta Amount of time to add in seconds
*/
public async increaseTimeAsync(timeDelta: number): Promise<number> {
assert.isNumber('timeDelta', timeDelta);
// Detect Geth vs. Ganache and use appropriate endpoint.
const version = await this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) {
@@ -332,11 +432,11 @@ export class Web3Wrapper {
public async getLogsAsync(filter: FilterObject): Promise<LogEntry[]> {
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = this._web3.toHex(fromBlock);
fromBlock = utils.numberToHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = this._web3.toHex(toBlock);
toBlock = utils.numberToHex(toBlock);
}
const serializedFilter = {
...filter,
@@ -344,30 +444,27 @@ export class Web3Wrapper {
toBlock,
};
const payload = {
jsonrpc: '2.0',
method: 'eth_getLogs',
params: [serializedFilter],
};
const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload);
const formattedLogs = _.map(rawLogs, this._formatLog.bind(this));
const formattedLogs = _.map(rawLogs, marshaller.unmarshalLog.bind(marshaller));
return formattedLogs;
}
/**
* Get a Web3 contract factory instance for a given ABI
* @param abi Smart contract ABI
* @returns Web3 contract factory which can create Web3 Contract instances from the supplied ABI
*/
public getContractFromAbi(abi: ContractAbi): Web3.Contract<any> {
const web3Contract = this._web3.eth.contract(abi);
return web3Contract;
}
/**
* Calculate the estimated gas cost for a given transaction
* @param txData Transaction data
* @returns Estimated gas cost
*/
public async estimateGasAsync(txData: Partial<TxData>): Promise<number> {
const gas = await promisify<number>(this._web3.eth.estimateGas)(txData);
assert.doesConformToSchema('txData', txData, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
const txDataHex = marshaller.marshalTxData(txData);
const gasHex = await this._sendRawPayloadAsync<string>({ method: 'eth_estimateGas', params: [txDataHex] });
const gas = utils.convertHexToNumber(gasHex);
return gas;
}
/**
@@ -377,7 +474,20 @@ export class Web3Wrapper {
* @returns The raw call result
*/
public async callAsync(callData: CallData, defaultBlock?: BlockParam): Promise<string> {
const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock);
assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
if (!_.isUndefined(defaultBlock)) {
Web3Wrapper._assertBlockParam(defaultBlock);
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const callDataHex = marshaller.marshalCallData(callData);
const rawCallResult = await this._sendRawPayloadAsync<string>({
method: 'eth_call',
params: [callDataHex, marshalledDefaultBlock],
});
if (rawCallResult === '0x') {
throw new Error('Contract call failed (returned null)');
}
@@ -389,7 +499,13 @@ export class Web3Wrapper {
* @returns Transaction hash
*/
public async sendTransactionAsync(txData: TxData): Promise<string> {
const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData);
assert.doesConformToSchema('txData', txData, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
const txDataHex = marshaller.marshalTxData(txData);
const txHash = await this._sendRawPayloadAsync<string>({ method: 'eth_sendTransaction', params: [txDataHex] });
return txHash;
}
/**
@@ -408,6 +524,11 @@ export class Web3Wrapper {
pollingIntervalMs: number = 1000,
timeoutMs?: number,
): 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.
let transactionReceipt = await this.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
@@ -493,7 +614,8 @@ export class Web3Wrapper {
* @param blockNumber The block number to reset to.
*/
public async setHeadAsync(blockNumber: number): Promise<void> {
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [this._web3.toHex(blockNumber)] });
assert.isNumber('blockNumber', blockNumber);
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [utils.numberToHex(blockNumber)] });
}
/**
* Returns either NodeType.Geth or NodeType.Ganache depending on the type of
@@ -510,7 +632,7 @@ export class Web3Wrapper {
}
}
private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {
const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider);
const sendAsync = this._provider.sendAsync.bind(this._provider);
const payloadWithDefaults = {
id: this._jsonRpcRequestId++,
params: [],
@@ -521,34 +643,4 @@ export class Web3Wrapper {
const result = response.result;
return result;
}
private _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 {
// Transaction status might have four values
// undefined - Testrpc and other old clients
// null - New clients on old transactions
// number - Parity
// hex - Geth
if (_.isString(status)) {
return this._web3.toDecimal(status) as 0 | 1;
} else if (_.isUndefined(status)) {
return null;
} else {
return status;
}
}
private _formatLog(rawLog: RawLogEntry): LogEntry {
const formattedLog = {
...rawLog,
logIndex: this._hexToDecimal(rawLog.logIndex),
blockNumber: this._hexToDecimal(rawLog.blockNumber),
transactionIndex: this._hexToDecimal(rawLog.transactionIndex),
};
return formattedLog;
}
private _hexToDecimal(hex: string | null): number | null {
if (_.isNull(hex)) {
return null;
}
const decimal = this._web3.toDecimal(hex);
return decimal;
}
} // tslint:disable-line:max-file-line-count

View File

@@ -1,18 +1,27 @@
import * as chai from 'chai';
import { BlockParamLiteral } from 'ethereum-types';
import * as Ganache from 'ganache-core';
import * as _ from 'lodash';
import 'mocha';
import { Web3Wrapper } from '../src';
import { utils } from '../src/utils';
import { Web3Wrapper } from '../src/web3_wrapper';
import { chaiSetup } from './utils/chai_setup';
chaiSetup.configure();
const { expect } = chai;
const NUM_GANACHE_ADDRESSES = 10;
describe('Web3Wrapper tests', () => {
const NETWORK_ID = 50;
const provider = Ganache.provider({ network_id: NETWORK_ID });
const web3Wrapper = new Web3Wrapper(provider);
let addresses: string[];
before(async () => {
addresses = await web3Wrapper.getAvailableAddressesAsync();
});
describe('#isAddress', () => {
it('correctly checks if a string is a valid ethereum address', () => {
expect(Web3Wrapper.isAddress('0x0')).to.be.false();
@@ -36,4 +45,88 @@ describe('Web3Wrapper tests', () => {
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 availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
expect(availableAddresses.length).to.be.equal(NUM_GANACHE_ADDRESSES);
expect(Web3Wrapper.isAddress(availableAddresses[0])).to.equal(true);
});
});
describe('#getBalanceInWeiAsync', () => {
it('gets the users balance in wei', async () => {
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('#signMessageAsync', () => {
it('should sign message', async () => {
const message = '0xdeadbeef';
const signer = addresses[1];
const signature = await web3Wrapper.signMessageAsync(signer, message);
const signatureLength = 132;
expect(signature.length).to.be.equal(signatureLength);
});
});
describe('#getBlockNumberAsync', () => {
it('get block number', async () => {
const blockNumber = await web3Wrapper.getBlockNumberAsync();
expect(typeof blockNumber).to.be.equal('number');
});
});
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);
expect(utils.isBigNumber(block.difficulty)).to.equal(true);
expect(_.isNumber(block.gasLimit)).to.equal(true);
});
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();
});
});
describe('#getBlockWithTransactionDataAsync', () => {
it('gets block when supplied a valid BlockParamLiteral value', async () => {
const blockParamLiteral = BlockParamLiteral.Earliest;
const block = await web3Wrapper.getBlockWithTransactionDataAsync(blockParamLiteral);
expect(block.number).to.be.equal(0);
expect(utils.isBigNumber(block.difficulty)).to.equal(true);
expect(_.isNumber(block.gasLimit)).to.equal(true);
});
it('should throw if supplied invalid blockParam value', async () => {
const invalidBlockParam = 'deadbeef';
expect(web3Wrapper.getBlockWithTransactionDataAsync(invalidBlockParam)).to.eventually.to.be.rejected();
});
});
describe('#getBlockTimestampAsync', () => {
it('gets block timestamp', async () => {
const blockParamLiteral = BlockParamLiteral.Earliest;
const timestamp = await web3Wrapper.getBlockTimestampAsync(blockParamLiteral);
expect(_.isNumber(timestamp)).to.be.equal(true);
});
});
});

View File

@@ -12721,16 +12721,6 @@ web3@^0.18.0:
xhr2 "*"
xmlhttprequest "*"
web3@^0.20.0:
version "0.20.6"
resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.6.tgz#3e97306ae024fb24e10a3d75c884302562215120"
dependencies:
bignumber.js "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
crypto-js "^3.1.4"
utf8 "^2.1.1"
xhr2 "*"
xmlhttprequest "*"
web3@^1.0.0-beta.34:
version "1.0.0-beta.34"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.34.tgz#347e561b784098cb5563315f490479a1d91f2ab1"