aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src/transaction_decoder.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils/src/transaction_decoder.ts')
-rw-r--r--packages/utils/src/transaction_decoder.ts80
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;
+ }
+}
+