diff options
Diffstat (limited to 'packages/utils/src/transaction_decoder.ts')
-rw-r--r-- | packages/utils/src/transaction_decoder.ts | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts new file mode 100644 index 000000000..85d92d553 --- /dev/null +++ b/packages/utils/src/transaction_decoder.ts @@ -0,0 +1,80 @@ +import { AbiDefinition, MethodAbi } from 'ethereum-types'; +import * as _ from 'lodash'; +import { AbiEncoder } from '.'; +import { FunctionInfoBySelector, TransactionData, TransactionProperties, DeployedContractInfo } from './types'; + +export class TransactionDecoder { + private _functionInfoBySelector: FunctionInfoBySelector = {}; + + private static getFunctionSelector(calldata: string): string { + if (!calldata.startsWith('0x') || calldata.length < 10) { + throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); + } + const functionSelector = calldata.substr(0, 10); + return functionSelector; + } + + public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { + if (_.isEmpty(abiArray)) { + return; + } + const functionAbis = _.filter(abiArray, abiEntry => { + return abiEntry.type === 'function'; + }) as MethodAbi[]; + _.each(functionAbis, functionAbi => { + const abiEncoder = new AbiEncoder.Method(functionAbi); + const functionSelector = abiEncoder.getSelector(); + if (!(functionSelector in this._functionInfoBySelector)) { + this._functionInfoBySelector[functionSelector] = []; + } + // Recored deployed versions of this decoder + const functionSignature = abiEncoder.getSignature(); + _.each(deploymentInfos, deploymentInfo => { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + contractAddress: deploymentInfo.contractAddress, + networkId: deploymentInfo.networkId, + }); + }); + // If there isn't a deployed version of this contract, record it without address/network id + if (_.isEmpty(deploymentInfos)) { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + }); + } + }); + } + + public decode(calldata: string, txProperties_?: TransactionProperties): TransactionData { + const functionSelector = TransactionDecoder.getFunctionSelector(calldata); + const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; + const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; + if (_.isUndefined(candidateFunctionInfos)) { + throw new Error(`No functions registered for selector '${functionSelector}'`); + } + const functionInfo = _.find(candidateFunctionInfos, (txDecoder) => { + return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && + (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && + (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); + }); + if (_.isUndefined(functionInfo)) { + throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); + } else if (_.isUndefined(functionInfo.abiEncoder)) { + throw new Error(`Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`); + } + const functionName = functionInfo.abiEncoder.getDataItem().name; + const functionSignature = functionInfo.abiEncoder.getSignatureType(); + const functionArguments = functionInfo.abiEncoder.decode(calldata); + const decodedCalldata = { + functionName, + functionSignature, + functionArguments + } + return decodedCalldata; + } +} + |