import { addressUtils, BigNumber } 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 web3Utils = require('web3-utils'); import { utils } from './utils'; import { AbstractBlockRPC, BlockWithoutTransactionDataRPC, BlockWithTransactionDataRPC, CallDataRPC, CallTxDataBaseRPC, TransactionRPC, TxDataRPC, } from './types'; export const marshaller = { unmarshalIntoBlockWithoutTransactionData( blockWithHexValues: BlockWithoutTransactionDataRPC, ): BlockWithoutTransactionData { const block = { ...blockWithHexValues, gasLimit: web3Utils.toDecimal(blockWithHexValues.gasLimit), gasUsed: web3Utils.toDecimal(blockWithHexValues.gasUsed), size: web3Utils.toDecimal(blockWithHexValues.size), timestamp: web3Utils.toDecimal(blockWithHexValues.timestamp), number: _.isNull(blockWithHexValues.number) ? null : web3Utils.toDecimal(blockWithHexValues.number), difficulty: this._convertAmountToBigNumber(blockWithHexValues.difficulty), totalDifficulty: this._convertAmountToBigNumber(blockWithHexValues.totalDifficulty), }; return block; }, unmarshalIntoBlockWithTransactionData(blockWithHexValues: BlockWithTransactionDataRPC): BlockWithTransactionData { const block = { ...blockWithHexValues, gasLimit: web3Utils.toDecimal(blockWithHexValues.gasLimit), gasUsed: web3Utils.toDecimal(blockWithHexValues.gasUsed), size: web3Utils.toDecimal(blockWithHexValues.size), timestamp: web3Utils.toDecimal(blockWithHexValues.timestamp), number: _.isNull(blockWithHexValues.number) ? null : web3Utils.toDecimal(blockWithHexValues.number), difficulty: this._convertAmountToBigNumber(blockWithHexValues.difficulty), totalDifficulty: this._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) ? web3Utils.toDecimal(txRpc.blockNumber) : null, transactionIndex: !_.isNull(txRpc.transactionIndex) ? web3Utils.toDecimal(txRpc.transactionIndex) : null, nonce: web3Utils.toDecimal(txRpc.nonce), gas: web3Utils.toDecimal(txRpc.gas), gasPrice: this._convertAmountToBigNumber(txRpc.gasPrice), value: this._convertAmountToBigNumber(txRpc.value), }; return tx; }, marshalTxData(txData: Partial): Partial { if (_.isUndefined(txData.from)) { throw new Error(`txData is missing required "from" address.`); } 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): Partial { 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) ? web3Utils.toHex(blockParam) : blockParam; return encodedBlockParam; }, unmarshalLog(rawLog: RawLogEntry): LogEntry { const formattedLog = { ...rawLog, logIndex: this.convertHexToNumberOrNull(rawLog.logIndex), blockNumber: this.convertHexToNumberOrNull(rawLog.blockNumber), transactionIndex: this.convertHexToNumberOrNull(rawLog.transactionIndex), }; return formattedLog; }, _marshalCallTxDataBase(callTxDataBase: Partial): Partial { const callTxDataBaseRPC = { ...callTxDataBase, to: _.isUndefined(callTxDataBase.to) ? undefined : this.marshalAddress(callTxDataBase.to), gasPrice: _.isUndefined(callTxDataBase.gasPrice) ? undefined : this._encodeAmountAsHexString(callTxDataBase.gasPrice), gas: _.isUndefined(callTxDataBase.gas) ? undefined : this._encodeAmountAsHexString(callTxDataBase.gas), value: _.isUndefined(callTxDataBase.value) ? undefined : this._encodeAmountAsHexString(callTxDataBase.value), nonce: _.isUndefined(callTxDataBase.nonce) ? undefined : this._encodeAmountAsHexString(callTxDataBase.nonce), }; return callTxDataBaseRPC; }, convertHexToNumberOrNull(hex: string | null): number | null { if (_.isNull(hex)) { return null; } const decimal = web3Utils.toDecimal(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 = this._convertAmountToBigNumber(value); const hexBase = 16; const valueHex = valueBigNumber.toString(hexBase); return valueBigNumber.lessThan(0) ? '-0x' + valueHex.substr(1) : '0x' + valueHex; }, };