Move BaseContract to web3Wrapper

This commit is contained in:
Leonid Logvinov 2018-02-27 12:01:12 -08:00
parent a0390956a9
commit 8fe844bcc9
No known key found for this signature in database
GPG Key ID: 0DD294BFDE8C95D4
10 changed files with 194 additions and 272 deletions

View File

@ -92,7 +92,6 @@
"ethereumjs-abi": "^0.6.4",
"ethereumjs-blockstream": "^2.0.6",
"ethereumjs-util": "^5.1.1",
"ethers-contracts": "^2.2.1",
"js-sha3": "^0.7.0",
"lodash": "^4.17.4",
"uuid": "^3.1.0",

View File

@ -1,6 +1 @@
dummy_token.ts
ether_token.ts
exchange.ts
token_registry.ts
token_transfer_proxy.ts
token.ts
*

View File

@ -6,13 +6,11 @@
// tslint:disable-next-line:no-unused-variable
import { TxData, TxDataPayable } from '@0xproject/types';
import { BigNumber, classUtils, promisify } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { BaseContract, Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethersContracts from 'ethers-contracts';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {BaseContract} from './base_contract';
{{#if events}}
export type {{contractName}}ContractEventArgs =
{{#each events}}

View File

@ -1,8 +1 @@
dummy_token.ts
exchange.ts
multi_sig_wallet_with_time_lock_except_remove_authorized_address.ts
multi_sig_wallet_with_time_lock.ts
multi_sig_wallet.ts
token_registry.ts
token_transfer_proxy.ts
zrx_token.ts
*

View File

@ -1,70 +0,0 @@
import { TxData, TxDataPayable } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethersContracts from 'ethers-contracts';
import * as _ from 'lodash';
import * as Web3 from 'web3';
export class BaseContract {
protected _ethersInterface: ethersContracts.Interface;
protected _web3Wrapper: Web3Wrapper;
public abi: Web3.ContractAbi;
public address: string;
protected static _transformABIData(
abis: Web3.DataItem[],
values: any[],
transformation: (type: string, value: any) => any,
): any {
return _.map(values, (value: any, i: number) =>
BaseContract._transformTypedData(abis[i].type, value, transformation),
);
}
protected static _lowercaseAddress(type: string, value: string): string {
return type === 'address' ? value.toLowerCase() : value;
}
protected static _bigNumberToString(type: string, value: string): string {
return _.isObject(value) && (value as any).isBigNumber ? value.toString() : value;
}
private static _transformTypedData(
type: string,
values: any,
transformation: (type: string, value: any) => any,
): any {
const trailingArrayRegex = /\[\d*\]$/;
if (type.match(trailingArrayRegex)) {
const arrayItemType = type.replace(trailingArrayRegex, '');
return _.map(values, (value: any, i: number) =>
this._transformTypedData(arrayItemType, value, transformation),
);
} else {
return transformation(type, values);
}
}
protected async _applyDefaultsToTxDataAsync<T extends Partial<TxData | TxDataPayable>>(
txData: T,
estimateGasAsync?: (txData: T) => Promise<number>,
): Promise<TxData> {
// Gas amount sourced with the following priorities:
// 1. Optional param passed in to public method call
// 2. Global config passed in at library instantiation
// 3. Gas estimate calculation + safety margin
const removeUndefinedProperties = _.pickBy;
const txDataWithDefaults = {
to: this.address,
...removeUndefinedProperties(this._web3Wrapper.getContractDefaults()),
...removeUndefinedProperties(txData as any),
// HACK: TS can't prove that T is spreadable.
// Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged
};
if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) {
const estimatedGas = await estimateGasAsync(txData);
txDataWithDefaults.gas = estimatedGas;
}
return txDataWithDefaults;
}
constructor(web3Wrapper: Web3Wrapper, abi: Web3.ContractAbi, address: string) {
this._web3Wrapper = web3Wrapper;
this.abi = abi;
this.address = address;
this._ethersInterface = new ethersContracts.Interface(abi);
}
}

View File

@ -26,11 +26,13 @@
"shx": "^0.2.2",
"tslint": "5.8.0",
"typescript": "2.7.1",
"ethers-typescript-typings": "^0.0.1",
"web3-typescript-typings": "^0.9.11"
},
"dependencies": {
"@0xproject/types": "^0.2.3",
"@0xproject/utils": "^0.3.4",
"ethers-contracts": "^2.2.1",
"lodash": "^4.17.4",
"web3": "^0.20.0"
}

View File

@ -1,9 +1,10 @@
import { TxData, TxDataPayable } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethersContracts from 'ethers-contracts';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import { Web3Wrapper } from './web3_wrapper';
export class BaseContract {
protected _ethersInterface: ethersContracts.Interface;
protected _web3Wrapper: Web3Wrapper;
@ -32,9 +33,7 @@ export class BaseContract {
const trailingArrayRegex = /\[\d*\]$/;
if (type.match(trailingArrayRegex)) {
const arrayItemType = type.replace(trailingArrayRegex, '');
return _.map(values, value =>
this._transformTypedData(arrayItemType, value, transformation),
);
return _.map(values, value => this._transformTypedData(arrayItemType, value, transformation));
} else {
return transformation(type, values);
}

View File

@ -1,179 +1,2 @@
import { TransactionReceipt, TxData } from '@0xproject/types';
import { BigNumber, promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import * as Web3 from 'web3';
interface RawLogEntry {
logIndex: string | null;
transactionIndex: string | null;
transactionHash: string;
blockHash: string | null;
blockNumber: string | null;
address: string;
data: string;
topics: string[];
}
export class Web3Wrapper {
private _web3: Web3;
private _defaults: Partial<TxData>;
private _jsonRpcRequestId: number;
constructor(provider: Web3.Provider, defaults?: Partial<TxData>) {
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`
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
(provider as any).sendAsync = (provider as any).send;
}
this._web3 = new Web3();
this._web3.setProvider(provider);
this._defaults = defaults || {};
this._jsonRpcRequestId = 0;
}
public getContractDefaults(): Partial<TxData> {
return this._defaults;
}
public setProvider(provider: Web3.Provider) {
this._web3.setProvider(provider);
}
public isAddress(address: string): boolean {
return this._web3.isAddress(address);
}
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
const addresses = await this.getAvailableAddressesAsync();
const normalizedAddress = senderAddress.toLowerCase();
return _.includes(addresses, normalizedAddress);
}
public async getNodeVersionAsync(): Promise<string> {
const nodeVersion = await promisify<string>(this._web3.version.getNode)();
return nodeVersion;
}
public async getNetworkIdAsync(): Promise<number> {
const networkIdStr = await promisify<string>(this._web3.version.getNetwork)();
const networkId = _.parseInt(networkIdStr);
return networkId;
}
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash);
if (!_.isNull(transactionReceipt)) {
transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status);
}
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
return this._web3.currentProvider;
}
public toWei(ethAmount: BigNumber): BigNumber {
const balanceWei = this._web3.toWei(ethAmount, 'ether');
return balanceWei;
}
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner);
// Rewrap in a new BigNumber
balanceInWei = new BigNumber(balanceInWei);
return balanceInWei;
}
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
const code = await promisify<string>(this._web3.eth.getCode)(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const codeIsEmpty = /^0x0{0,40}$/i.test(code);
return !codeIsEmpty;
}
public async signTransactionAsync(address: string, message: string): Promise<string> {
const signData = await promisify<string>(this._web3.eth.sign)(address, message);
return signData;
}
public async getBlockNumberAsync(): Promise<number> {
const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)();
return blockNumber;
}
public async getBlockAsync(blockParam: string | Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
const block = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam);
return block;
}
public async getBlockTimestampAsync(blockParam: string | Web3.BlockParam): Promise<number> {
const { timestamp } = await this.getBlockAsync(blockParam);
return timestamp;
}
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses = await promisify<string[]>(this._web3.eth.getAccounts)();
const normalizedAddresses = _.map(addresses, address => address.toLowerCase());
return normalizedAddresses;
}
public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = this._web3.toHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = this._web3.toHex(toBlock);
}
const serializedFilter = {
...filter,
fromBlock,
toBlock,
};
const payload = {
jsonrpc: '2.0',
id: this._jsonRpcRequestId++,
method: 'eth_getLogs',
params: [serializedFilter],
};
const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload);
const formattedLogs = _.map(rawLogs, this._formatLog.bind(this));
return formattedLogs;
}
public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<any> {
const web3Contract = this._web3.eth.contract(abi);
return web3Contract;
}
public async estimateGasAsync(txData: Partial<Web3.TxData>): Promise<number> {
const gas = await promisify<number>(this._web3.eth.estimateGas)(txData);
return gas;
}
public async callAsync(callData: Web3.CallData, defaultBlock?: Web3.BlockParam): Promise<string> {
const rawCalllResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock);
return rawCalllResult;
}
public async sendTransactionAsync(txData: Web3.TxData): Promise<string> {
const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData);
return txHash;
}
private async _sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> {
const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider);
const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload);
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): Web3.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;
}
}
export { Web3Wrapper } from './web3_wrapper';
export { BaseContract } from './base_contract';

View File

@ -0,0 +1,179 @@
import { TransactionReceipt, TxData } from '@0xproject/types';
import { BigNumber, promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import * as Web3 from 'web3';
interface RawLogEntry {
logIndex: string | null;
transactionIndex: string | null;
transactionHash: string;
blockHash: string | null;
blockNumber: string | null;
address: string;
data: string;
topics: string[];
}
export class Web3Wrapper {
private _web3: Web3;
private _defaults: Partial<TxData>;
private _jsonRpcRequestId: number;
constructor(provider: Web3.Provider, defaults?: Partial<TxData>) {
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`
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
(provider as any).sendAsync = (provider as any).send;
}
this._web3 = new Web3();
this._web3.setProvider(provider);
this._defaults = defaults || {};
this._jsonRpcRequestId = 0;
}
public getContractDefaults(): Partial<TxData> {
return this._defaults;
}
public setProvider(provider: Web3.Provider) {
this._web3.setProvider(provider);
}
public isAddress(address: string): boolean {
return this._web3.isAddress(address);
}
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
const addresses = await this.getAvailableAddressesAsync();
const normalizedAddress = senderAddress.toLowerCase();
return _.includes(addresses, normalizedAddress);
}
public async getNodeVersionAsync(): Promise<string> {
const nodeVersion = await promisify<string>(this._web3.version.getNode)();
return nodeVersion;
}
public async getNetworkIdAsync(): Promise<number> {
const networkIdStr = await promisify<string>(this._web3.version.getNetwork)();
const networkId = _.parseInt(networkIdStr);
return networkId;
}
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash);
if (!_.isNull(transactionReceipt)) {
transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status);
}
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
return this._web3.currentProvider;
}
public toWei(ethAmount: BigNumber): BigNumber {
const balanceWei = this._web3.toWei(ethAmount, 'ether');
return balanceWei;
}
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner);
// Rewrap in a new BigNumber
balanceInWei = new BigNumber(balanceInWei);
return balanceInWei;
}
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
const code = await promisify<string>(this._web3.eth.getCode)(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const codeIsEmpty = /^0x0{0,40}$/i.test(code);
return !codeIsEmpty;
}
public async signTransactionAsync(address: string, message: string): Promise<string> {
const signData = await promisify<string>(this._web3.eth.sign)(address, message);
return signData;
}
public async getBlockNumberAsync(): Promise<number> {
const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)();
return blockNumber;
}
public async getBlockAsync(blockParam: string | Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
const block = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam);
return block;
}
public async getBlockTimestampAsync(blockParam: string | Web3.BlockParam): Promise<number> {
const { timestamp } = await this.getBlockAsync(blockParam);
return timestamp;
}
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses = await promisify<string[]>(this._web3.eth.getAccounts)();
const normalizedAddresses = _.map(addresses, address => address.toLowerCase());
return normalizedAddresses;
}
public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = this._web3.toHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = this._web3.toHex(toBlock);
}
const serializedFilter = {
...filter,
fromBlock,
toBlock,
};
const payload = {
jsonrpc: '2.0',
id: this._jsonRpcRequestId++,
method: 'eth_getLogs',
params: [serializedFilter],
};
const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload);
const formattedLogs = _.map(rawLogs, this._formatLog.bind(this));
return formattedLogs;
}
public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<any> {
const web3Contract = this._web3.eth.contract(abi);
return web3Contract;
}
public async estimateGasAsync(txData: Partial<Web3.TxData>): Promise<number> {
const gas = await promisify<number>(this._web3.eth.estimateGas)(txData);
return gas;
}
public async callAsync(callData: Web3.CallData, defaultBlock?: Web3.BlockParam): Promise<string> {
const rawCalllResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock);
return rawCalllResult;
}
public async sendTransactionAsync(txData: Web3.TxData): Promise<string> {
const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData);
return txHash;
}
private async _sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> {
const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider);
const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload);
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): Web3.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;
}
}

View File

@ -3,5 +3,9 @@
"compilerOptions": {
"outDir": "lib"
},
"include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"]
"include": [
"./src/**/*",
"../../node_modules/ethers-typescript-typings/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts"
]
}