Skip to content

Instantly share code, notes, and snippets.

@HenryNguyen5
Created September 5, 2017 23:12
Show Gist options
  • Save HenryNguyen5/f4862c3a43de9d1ee4ff117376ae5165 to your computer and use it in GitHub Desktop.
Save HenryNguyen5/f4862c3a43de9d1ee4ff117376ae5165 to your computer and use it in GitHub Desktop.
ABI Function parser that maps an ABI function to an object that contains call and decode methods, and allows for run-time checking/parsing of given arguments
import abi from 'ethereumjs-abi';
class AbiFunction {
constant;
inputs;
name;
outputs;
payable;
type;
methodInputTypes;
inputNames;
methodSelector;
funcParams;
constructor(abiFunc) {
Object.assign(this, abiFunc);
this._init();
}
_init() {
const { inputs } = this;
this.funcParams = this._makeFuncParams();
//TODO: do this in O(n)
this.methodInputTypes = inputs.map(({ type }) => type);
this.inputNames = inputs.map(({ name }) => name);
this.methodSelector = abi
.methodID(this.name, this.methodInputTypes)
.toString('hex');
}
call = (suppliedInputs = {}) => {
const { _processSuppliedArgs, _makeEncodedFuncCall } = this;
const args = _processSuppliedArgs(suppliedInputs);
const encodedCall = _makeEncodedFuncCall(args);
return encodedCall;
};
decode = argString => {
const { methodSelector, methodInputTypes, inputNames } = this;
// Remove method selector from data, if present
argString = argString.replace(`0x${methodSelector}`, '');
// Convert argdata to a hex buffer for ethereumjs-abi
const argBuffer = new Buffer(argString, 'hex');
// Decode!
const argArr = abi.rawDecode(methodInputTypes, argBuffer);
//TODO: parse checksummed addresses
return argArr.reduce(
(argObj, currArg, index) => ({
...argObj,
[inputNames[index]]: currArg
}),
{}
);
};
_makeFuncParams = () =>
this.inputs.reduce((inputs, currInput) => {
const { name, type } = currInput;
const inputHandler = inputToParse => {
//TODO: introduce typechecking and typecasting mapping for inputs
const value = inputToParse;
return { name, type, value };
};
return { ...inputs, [name]: { processInput: inputHandler, type } };
}, {});
_makeEncodedFuncCall = args => {
const { methodSelector, methodInputTypes } = this;
const encodedArgs = abi.rawEncode(methodInputTypes, args).toString('hex');
return `0x${methodSelector}${encodedArgs}`;
};
_processSuppliedArgs = suppliedArgs => {
const { inputNames, funcParams } = this;
return inputNames.map(name => {
const type = funcParams[name].type;
//TODO: parse args based on type
if (!suppliedArgs[name])
throw Error(`Expected argument "${name}" of type "${type}" missing`);
const value = suppliedArgs[name];
const processedArg = funcParams[name].processInput(value);
return processedArg.value;
});
};
}
@HenryNguyen5
Copy link
Author

HenryNguyen5 commented Sep 5, 2017

What this does:

Takes in a function of a contract interfaces and returns an object with .call and .decode methods.

.call: Accepts key-value parameters and checks them at run-time against the given ABI function to see that sufficient parameters have been given, also allows for run-time parsing of supplied parameters for things such as BN, checking for sufficient address length, making sure numbers are converted into BN, etc...

.decode: accepts an encoded argument string and decodes it, returning an object with key-value pairs with a 1-1 mapping to the specific function in the ABI

Why?

  • Allow for much stricter checking of parameters before costly JSON-RPC calls
  • Reduces error potential by accepting everything as an object with 1-1 ABI mapping instead of guessing at parameters
  • Much better error messages when missing parameters or using the wrong type of parameter.
  • Stream line our contract usage to one standard

Composition potential

A Contract class could use this class to make its functions, then wrap them in calls to easily generate JSON-RPC objects with the right values since each function object knows all of its own metadata such as constant payable etc...

TODO

  • Implement aforementioned parsing and validation of parameters
  • Add flow typing
  • Either make a base-class that handles constructor fallback ABI functions or handle them all in one class
  • Handle return values that would come from the node JSON-RPC call (do we need this?)
  • Make an event ABI handler class similar to this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment