aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2018-11-09 02:31:30 +0800
committerGreg Hysen <greg.hysen@gmail.com>2018-11-29 08:38:10 +0800
commit637ab1076a4071505ea3d4797767826070a65e16 (patch)
tree04a8fe1a71e0f437c965c8791e18ff1fc5d32bd3
parenta13099bde3aa6a47516127ae7c7d9f78488a3911 (diff)
downloaddexon-sol-tools-637ab1076a4071505ea3d4797767826070a65e16.tar.gz
dexon-sol-tools-637ab1076a4071505ea3d4797767826070a65e16.tar.zst
dexon-sol-tools-637ab1076a4071505ea3d4797767826070a65e16.zip
ABI Encoding for all combinations of arrays
-rw-r--r--packages/order-utils/test/abi_encoder.ts1098
-rw-r--r--packages/order-utils/test/abi_encoder_test.ts1336
-rw-r--r--packages/order-utils/test/abi_samples.ts358
3 files changed, 1501 insertions, 1291 deletions
diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts
new file mode 100644
index 000000000..55b63b701
--- /dev/null
+++ b/packages/order-utils/test/abi_encoder.ts
@@ -0,0 +1,1098 @@
+
+import * as chai from 'chai';
+import 'mocha';
+import ethUtil = require('ethereumjs-util');
+
+var _ = require('lodash');
+
+import { chaiSetup } from './utils/chai_setup';
+
+import { MethodAbi, DataItem } from 'ethereum-types';
+
+import { BigNumber } from '@0x/utils';
+import { bigNumberify } from 'ethers/utils';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+
+class Word {
+ private value: string;
+
+ constructor(value?: string) {
+ if (value === undefined) {
+ this.value = '';
+ } else {
+ this.value = value;
+ }
+ }
+
+ public set(value: string) {
+ if (value.length !== 64) {
+ throw `Tried to create word that is not 32 bytes: ${value}`;
+ }
+
+ this.value = value;
+ }
+
+ public get(): string {
+ return this.value;
+ }
+
+ public getAsHex(): string {
+ return `0x${this.value}`;
+ }
+}
+
+export enum CalldataSection {
+ NONE,
+ PARAMS,
+ DATA,
+}
+
+class Memblock {
+ private dataType: DataType;
+ private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber };
+
+ constructor(dataType: DataType) {
+ this.dataType = dataType;
+ this.location = {
+ calldataSection: CalldataSection.NONE,
+ sectionOffset: new BigNumber(0),
+ offset: new BigNumber(0),
+ };
+ }
+
+ public getSize(): BigNumber {
+ return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength);
+ }
+
+ public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) {
+ this.location.calldataSection = calldataSection;
+ this.location.sectionOffset = sectionOffset;
+ this.location.offset = offset;
+ }
+
+ public get(): string {
+ return ethUtil.stripHexPrefix(this.dataType.getHexValue());
+ }
+
+ public getOffset(): BigNumber {
+ return this.location.offset;
+ }
+
+ public getAbsoluteOffset(): BigNumber {
+ return this.location.sectionOffset.plus(this.location.offset);
+ }
+
+ public getSection(): CalldataSection {
+ return this.location.calldataSection;
+ }
+}
+
+interface BindList {
+ [key: string]: Memblock;
+}
+
+export class Calldata {
+ private selector: string;
+ private params: Memblock[];
+ private data: Memblock[];
+ private dataOffset: BigNumber;
+ private currentDataOffset: BigNumber;
+ private currentParamOffset: BigNumber;
+ private bindList: BindList;
+
+ constructor(selector: string, nParams: number) {
+ this.selector = selector;
+ this.params = [];
+ this.data = [];
+ const evmWordSize = 32;
+ this.dataOffset = new BigNumber(nParams).times(evmWordSize);
+ this.currentDataOffset = new BigNumber(0);
+ this.currentParamOffset = new BigNumber(0);
+ this.bindList = {};
+ }
+
+ public bind(dataType: DataType, section: CalldataSection) {
+ if (dataType.getId() in this.bindList) {
+ throw `Rebind on ${dataType.getId()}`;
+ }
+ const memblock = new Memblock(dataType);
+ switch (section) {
+ case CalldataSection.PARAMS:
+ this.params.push(memblock);
+ memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset);
+
+ console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`);
+ this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize());
+ break;
+
+ case CalldataSection.DATA:
+ this.data.push(memblock);
+ memblock.assignLocation(section, this.dataOffset, this.currentDataOffset);
+
+ console.log(
+ `Binding ${dataType.getDataItem().name} to DATA at ${memblock
+ .getAbsoluteOffset()
+ .toString(16)}`,
+ );
+ this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize());
+ break;
+
+ default:
+ throw `Unrecognized calldata section: ${section}`;
+ }
+
+ this.bindList[dataType.getId()] = memblock;
+ dataType.rbind(memblock);
+ }
+
+ public getHexValue(): string {
+ let hexValue = `${this.selector}`;
+ _.each(this.params, (memblock: Memblock) => {
+ hexValue += memblock.get();
+ });
+ _.each(this.data, (memblock: Memblock) => {
+ hexValue += memblock.get();
+ });
+
+ return hexValue;
+ }
+}
+
+export abstract class DataType {
+ private dataItem: DataItem;
+ private hexValue: string;
+ protected memblock: Memblock | undefined;
+ protected children: DataType[];
+
+ constructor(dataItem: DataItem) {
+ this.dataItem = dataItem;
+ this.hexValue = '0x';
+ this.memblock = undefined;
+ this.children = [];
+ }
+
+ protected assignHexValue(hexValue: string) {
+ this.hexValue = hexValue;
+ }
+
+ public getHexValue(): string {
+ return this.hexValue;
+ }
+
+ public getDataItem(): DataItem {
+ return this.dataItem;
+ }
+
+ public rbind(memblock: Memblock) {
+ this.memblock = memblock;
+ }
+
+ public bind(calldata: Calldata, section: CalldataSection) {
+ calldata.bind(this, section);
+ _.each(this.getChildren(), (child: DataType) => {
+ child.bind(calldata, CalldataSection.DATA);
+ });
+ }
+
+ public getId(): string {
+ return this.dataItem.name;
+ }
+
+ public getOffset(): BigNumber {
+ if (this.memblock === undefined) return new BigNumber(0);
+ return this.memblock.getOffset();
+ }
+
+ public getAbsoluteOffset(): BigNumber {
+ if (this.memblock === undefined) return new BigNumber(0);
+ return this.memblock.getAbsoluteOffset();
+ }
+ /*
+ public getSize(): BigNumber {
+ if (this.memblock === undefined) return new BigNumber(0);
+ return this.memblock.getSize();
+ }
+ */
+
+ public getChildren(): DataType[] {
+ return this.children;
+ }
+
+ public getSize(): BigNumber {
+ return this.getHeaderSize().plus(this.getBodySize());
+ }
+
+ public abstract assignValue(value: any): void;
+ public abstract getSignature(): string;
+ public abstract isStatic(): boolean;
+ public abstract getHeaderSize(): BigNumber;
+ public abstract getBodySize(): BigNumber;
+}
+
+export abstract class StaticDataType extends DataType {
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ }
+}
+
+export abstract class DynamicDataType extends DataType {
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ }
+}
+
+export class Address extends StaticDataType {
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ expect(Address.matchGrammar(dataItem.type)).to.be.true();
+ }
+
+ public assignValue(value: string) {
+ const evmWordWidth = 32;
+ const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth));
+ this.assignHexValue(hexValue);
+ }
+
+ public getSignature(): string {
+ return `address`;
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(32);
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'address';
+ }
+}
+
+export class Bool extends StaticDataType {
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ expect(Bool.matchGrammar(dataItem.type)).to.be.true();
+ }
+
+ public assignValue(value: boolean) {
+ const evmWordWidth = 32;
+ const encodedValue = value === true ? '0x1' : '0x0';
+ const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth));
+ this.assignHexValue(hexValue);
+ }
+
+ public getSignature(): string {
+ return 'bool';
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(32);
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'bool';
+ }
+}
+
+abstract class Number extends StaticDataType {
+ static MAX_WIDTH: number = 256;
+ static DEFAULT_WIDTH: number = Number.MAX_WIDTH;
+ width: number = Number.DEFAULT_WIDTH;
+
+ constructor(dataItem: DataItem, matcher: RegExp) {
+ super(dataItem);
+ const matches = matcher.exec(dataItem.type);
+ expect(matches).to.be.not.null();
+ if (matches !== null && matches.length === 2 && matches[1] !== undefined) {
+ this.width = parseInt(matches[1]);
+ } else {
+ this.width = 256;
+ }
+ }
+
+ public assignValue(value: BigNumber) {
+ if (value.greaterThan(this.getMaxValue())) {
+ throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`;
+ } else if (value.lessThan(this.getMinValue())) {
+ throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`;
+ }
+
+ const hexBase = 16;
+ const evmWordWidth = 32;
+ let valueBuf: Buffer;
+ if (value.greaterThanOrEqualTo(0)) {
+ valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth);
+ } else {
+ // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves.
+ // Step 1/3: Convert value to positive binary string
+ const binBase = 2;
+ const valueBin = value.times(-1).toString(binBase);
+
+ // Step 2/3: Invert binary value
+ const bitsInEvmWord = 256;
+ let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length);
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, binBase);
+
+ // Step 3/3: Add 1 to inverted value
+ // The result is the two's-complement represent of the input value.
+ const negativeValue = invertedValue.plus(1);
+
+ // Convert the negated value to a hex string
+ valueBuf = ethUtil.setLengthLeft(
+ ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`),
+ evmWordWidth,
+ );
+ }
+
+ const encodedValue = ethUtil.bufferToHex(valueBuf);
+ this.assignHexValue(encodedValue);
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(32);
+ }
+
+ public abstract getMaxValue(): BigNumber;
+ public abstract getMinValue(): BigNumber;
+}
+
+export class Int extends Number {
+ static matcher = RegExp(
+ '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
+ );
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, Int.matcher);
+ }
+
+ public getMaxValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width - 1).sub(1);
+ }
+
+ public getMinValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width - 1).times(-1);
+ }
+
+ public getSignature(): string {
+ return `int${this.width}`;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class UInt extends Number {
+ static matcher = RegExp(
+ '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
+ );
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, UInt.matcher);
+ }
+
+ public getMaxValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width).sub(1);
+ }
+
+ public getMinValue(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getSignature(): string {
+ return `uint${this.width}`;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class Byte extends StaticDataType {
+ static matcher = RegExp(
+ '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$',
+ );
+
+ static DEFAULT_WIDTH = 1;
+ width: number = Byte.DEFAULT_WIDTH;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ const matches = Byte.matcher.exec(dataItem.type);
+ expect(matches).to.be.not.null();
+ if (matches !== null && matches.length === 3 && matches[2] !== undefined) {
+ this.width = parseInt(matches[2]);
+ } else {
+ this.width = Byte.DEFAULT_WIDTH;
+ }
+ }
+
+ public assignValue(value: string | Buffer) {
+ // Convert value into a buffer and do bounds checking
+ const valueBuf = ethUtil.toBuffer(value);
+ if (valueBuf.byteLength > this.width) {
+ throw new Error(
+ `Tried to assign ${value} (${
+ valueBuf.byteLength
+ } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`,
+ );
+ } else if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+
+ // Store value as hex
+ const evmWordWidth = 32;
+ const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth);
+ const hexValue = ethUtil.bufferToHex(paddedValue);
+ this.assignHexValue(hexValue);
+ }
+
+ public getSignature(): string {
+ // Note that `byte` reduces to `bytes1`
+ return `bytes${this.width}`;
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(32);
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class Bytes extends DynamicDataType {
+ static UNDEFINED_LENGTH = new BigNumber(-1);
+ length: BigNumber = Bytes.UNDEFINED_LENGTH;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ expect(Bytes.matchGrammar(dataItem.type)).to.be.true();
+ }
+
+ public assignValue(value: string | Buffer) {
+ if (typeof value === 'string' && !value.startsWith('0x')) {
+ throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+
+ const wordsForValue = Math.ceil(valueBuf.byteLength / 32);
+ const paddedBytesForValue = wordsForValue * 32;
+ const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
+ const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32);
+ const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
+ const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
+
+ this.assignHexValue(encodedValue);
+ }
+
+ public getSignature(): string {
+ return 'bytes';
+ }
+
+ public isStatic(): boolean {
+ return false;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(this.getHexValue().length);
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'bytes';
+ }
+}
+
+export class SolString extends DynamicDataType {
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ expect(SolString.matchGrammar(dataItem.type)).to.be.true();
+ }
+
+ public assignValue(value: string) {
+ const wordsForValue = Math.ceil(value.length / 32);
+ const paddedBytesForValue = wordsForValue * 32;
+ const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
+ const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32);
+ const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]);
+ const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
+
+ this.assignHexValue(encodedValue);
+ }
+
+ public getSignature(): string {
+ return 'string';
+ }
+
+ public isStatic(): boolean {
+ return false;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(this.getHexValue().length);
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'string';
+ }
+}
+
+export class SolArray extends DynamicDataType {
+ static matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
+ static UNDEFINED_LENGTH = new BigNumber(-1);
+ length: BigNumber = SolArray.UNDEFINED_LENGTH;
+ type: string = '[undefined]';
+ isLengthDefined: boolean;
+ isStaticArray: boolean; // An array is dynamic if it's lenghth is undefined or if its children are dynamic.
+ private elements: DataType[];
+
+ /*
+ --- Layout 1: Fixed Length Array with Static Types ---
+ Elem1, Elem2, ..., ElemN
+
+ --- Layout 2: Fixed Length Array with Dynamic Types ---
+ PtrToArray, ..., Elem1, Elem2, ..., ElemN
+
+ --- Layout 3: Dynamic Length Array with Static Types ---
+ PtrToArray, ..., ArrayLength, Elem1, Elem2, ..., ElemN
+
+ --- Layout 4: Dynamic Length Array with Dynamic Types ---
+ PtrToArray, ..., ArrayLength, PtrToElem1, PtrToElem2, ..., PtrToElemN, ..., Elem1, Elem2, ..., ElemN
+ */
+
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ const matches = SolArray.matcher.exec(dataItem.type);
+ expect(matches).to.be.not.null();
+ console.log(JSON.stringify(matches));
+ if (matches === null || matches.length !== 3) {
+ throw new Error(`Could not parse array: ${dataItem.type}`);
+ } else if (matches[1] === undefined) {
+ throw new Error(`Could not parse array type: ${dataItem.type}`);
+ } else if (matches[2] === undefined) {
+ throw new Error(`Could not parse array length: ${dataItem.type}`);
+ }
+
+ this.elements = [];
+
+ // Check if length is undefined
+ if (matches[2] === '') {
+ this.type = matches[1];
+ this.length = SolArray.UNDEFINED_LENGTH;
+ this.isLengthDefined = false;
+ this.isStaticArray = false;
+ return;
+ }
+
+ // Parse out array type/length and construct children
+ this.isLengthDefined = true;
+ this.type = matches[1];
+ this.length = new BigNumber(matches[2], 10);
+ if (this.length.lessThan(1)) {
+ throw new Error(`Bad array length: ${JSON.stringify(this.length)}`);
+ }
+ this.constructChildren();
+
+ // Check if we're static or not
+ this.isStaticArray = !(this.elements[0] instanceof Pointer); //this.elements[0].isStatic();
+ //throw new Error(`Am I static? ${this.isStaticArray}`);
+ }
+
+ private constructChildren() {
+ for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
+ const childDataItem = {
+ type: this.type,
+ name: `${this.getDataItem().name}[${idx.toString(10)}]`,
+ } as DataItem;
+ const child = DataTypeFactory.create(childDataItem, this);
+ this.elements.push(child);
+ if (child instanceof Pointer) {
+ const pointsTo = child.getChildren()[0];
+ console.log(JSON.stringify(pointsTo));
+ this.children.push(pointsTo); // DataType pointing to
+ }
+ }
+ }
+
+ // @TODO: HACKY -- shouldn't really have children for a
+ /*
+ public getChildren(): DataType[] {
+ if (this.isStatic()) {
+ return [];
+ } else {
+ return this.children;
+ }
+ }*/
+
+ public assignValue(value: any[]) {
+ // Sanity check length
+ const valueLength = new BigNumber(value.length);
+ if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) {
+ throw new Error(
+ `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify(
+ valueLength,
+ )}`,
+ );
+ }
+
+ // Assign length if not already set
+ if (this.length === SolArray.UNDEFINED_LENGTH) {
+ this.length = valueLength;
+ this.constructChildren();
+ }
+
+ // Assign values to children
+ for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
+ const idxNumber = idx.toNumber();
+ this.elements[idxNumber].assignValue(value[idxNumber]);
+ }
+ }
+
+ public getHexValue(): string {
+ let valueBuf = new Buffer("");
+
+ if (this.isLengthDefined === false) {
+ // Must include the array length
+ const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`);
+ const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32);
+ valueBuf = lengthBuf;
+ }
+
+ for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
+ const idxNumber = idx.toNumber();
+ const childValueHex = this.elements[idxNumber].getHexValue();
+ const childValueBuf = ethUtil.toBuffer(childValueHex);
+ valueBuf = Buffer.concat([valueBuf, childValueBuf]);
+ }
+
+ // Convert value buffer to hex
+ const valueHex = ethUtil.bufferToHex(valueBuf);
+ return valueHex;
+ }
+
+ public isStatic(): boolean {
+ return this.isStaticArray;
+ }
+
+ public getHeaderSize(): BigNumber {
+ let size = new BigNumber(0);
+ if (!this.isLengthDefined) {
+ size = new BigNumber(32); // stores length of bytes
+ }
+ return size;
+ }
+
+ public getBodySize(): BigNumber {
+ const evmWordWidth = new BigNumber(32);
+ const body = this.length.times(evmWordWidth);
+ return body;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+
+ public getSignature(): string {
+ let type = this.type;
+ if (this.type === 'tuple') {
+ let tupleDataItem = {
+ type: 'tuple',
+ name: 'N/A',
+ } as DataItem;
+ const tupleComponents = this.getDataItem().components;
+ if (tupleComponents !== undefined) {
+ tupleDataItem.components = tupleComponents;
+ }
+ const tuple = new Tuple(tupleDataItem);
+ type = tuple.getSignature();
+ }
+
+ if (this.length.equals(SolArray.UNDEFINED_LENGTH)) {
+ return `${type}[]`;
+ }
+ return `${type}[${this.length}]`;
+ }
+}
+
+export class Tuple extends DynamicDataType {
+ private length: BigNumber;
+ private childMap: { [key: string]: number };
+
+ constructor(dataItem: DataItem) {
+ super(dataItem);
+ expect(Tuple.matchGrammar(dataItem.type)).to.be.true();
+ this.length = new BigNumber(0);
+ this.childMap = {};
+ if (dataItem.components !== undefined) {
+ this.constructChildren(dataItem.components);
+ this.length = new BigNumber(dataItem.components.length);
+ } else {
+ throw new Error('Components undefined');
+ }
+ }
+
+ private constructChildren(dataItems: DataItem[]) {
+ _.each(dataItems, (dataItem: DataItem) => {
+ const childDataItem = {
+ type: dataItem.type,
+ name: `${this.getDataItem().name}.${dataItem.name}`,
+ } as DataItem;
+ const child = DataTypeFactory.create(childDataItem, this);
+ this.childMap[dataItem.name] = this.children.length;
+ this.children.push(child);
+ });
+ }
+
+ private assignValueFromArray(value: any[]) {
+ // Sanity check length
+ const valueLength = new BigNumber(value.length);
+ if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) {
+ throw new Error(
+ `Expected array of ${JSON.stringify(
+ this.length,
+ )} elements, but got array of length ${JSON.stringify(valueLength)}`,
+ );
+ }
+
+ // Assign values to children
+ for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
+ const idxNumber = idx.toNumber();
+ this.children[idxNumber].assignValue(value[idxNumber]);
+ }
+ }
+
+ private assignValueFromObject(obj: object) {
+ let childMap = _.cloneDeep(this.childMap);
+ _.forOwn(obj, (value: any, key: string) => {
+ if (key in childMap === false) {
+ throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`);
+ }
+ this.children[this.childMap[key]].assignValue(value);
+ delete childMap[key];
+ });
+
+ if (Object.keys(childMap).length !== 0) {
+ throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`);
+ }
+ }
+
+ public assignValue(value: any[] | object) {
+ if (value instanceof Array) {
+ this.assignValueFromArray(value);
+ } else if (typeof value === 'object') {
+ this.assignValueFromObject(value);
+ } else {
+ throw new Error(`Unexpected type for ${value}`);
+ }
+ }
+
+ public getHexValue(): string {
+ return '0x';
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ const evmWordWidth = new BigNumber(32);
+ const size = this.length.times(evmWordWidth);
+ return size;
+ }
+
+ public getSignature(): string {
+ // Compute signature
+ let signature = `(`;
+ _.each(this.children, (child: DataType, i: number) => {
+ signature += child.getSignature();
+ if (i < this.children.length - 1) {
+ signature += ',';
+ }
+ });
+ signature += ')';
+ return signature;
+ }
+
+ public isStatic(): boolean {
+ return false; // @TODO: True in every case or only when dynamic data?
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'tuple';
+ }
+}
+
+/* TODO
+class Fixed extends StaticDataType {}
+
+class UFixed extends StaticDataType {}*/
+
+export class Pointer extends StaticDataType {
+ destDataType: DynamicDataType;
+ parentDataType: DataType;
+
+ constructor(destDataType: DynamicDataType, parentDataType: DataType) {
+ const destDataItem = destDataType.getDataItem();
+ const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem;
+ super(dataItem);
+ this.destDataType = destDataType;
+ this.parentDataType = parentDataType;
+ this.children.push(destDataType);
+ }
+
+ /*
+ public assignValue(destDataType: DynamicDataType) {
+ this.destDataType = destDataType;
+ }*/
+
+ public assignValue(value: any) {
+ this.destDataType.assignValue(value);
+ }
+
+ public getHexValue(): string {
+ console.log(
+ '*'.repeat(40),
+ this.destDataType.getAbsoluteOffset().toString(16),
+ '^'.repeat(150),
+ this.parentDataType.getAbsoluteOffset().toString(16),
+ );
+
+ let offset = this.destDataType
+ .getAbsoluteOffset()
+ .minus(this.parentDataType.getAbsoluteOffset())
+ .minus(this.parentDataType.getHeaderSize());
+
+ console.log("OFFSET == ", JSON.stringify(offset), " or in hex -- 0x", offset.toString(16));
+
+ const hexBase = 16;
+ const evmWordWidth = 32;
+ const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth);
+ const encodedValue = ethUtil.bufferToHex(valueBuf);
+ return encodedValue;
+ }
+
+ public getSignature(): string {
+ return this.destDataType.getSignature();
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ return new BigNumber(32);
+ }
+
+}
+
+export class DataTypeFactory {
+ public static mapDataItemToDataType(dataItem: DataItem): DataType {
+ console.log(`Type: ${dataItem.type}`);
+
+ if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
+ if (Address.matchGrammar(dataItem.type)) return new Address(dataItem);
+ if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem);
+ if (Int.matchGrammar(dataItem.type)) return new Int(dataItem);
+ if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem);
+ if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem);
+ if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem);
+ if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
+ if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem);
+ if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem);
+ //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem);
+ //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem);
+
+ throw new Error(`Unrecognized data type: '${dataItem.type}'`);
+ }
+
+ public static create(dataItem: DataItem, parentDataType: DataType): DataType {
+ const dataType = DataTypeFactory.mapDataItemToDataType(dataItem);
+ if (dataType.isStatic()) {
+ return dataType;
+ } else {
+ const pointer = new Pointer(dataType, parentDataType);
+ return pointer;
+ }
+
+ throw new Error(`Unrecognized instance type: '${dataType}'`);
+ }
+}
+
+class Queue<T> {
+ private store: T[] = [];
+ push(val: T) {
+ this.store.push(val);
+ }
+ pop(): T | undefined {
+ return this.store.shift();
+ }
+}
+
+export class Method extends DataType {
+ name: string;
+ params: DataType[];
+ private signature: string;
+ selector: string;
+
+ constructor(abi: MethodAbi) {
+ super({ type: 'method', name: abi.name });
+ this.name = abi.name;
+ this.params = [];
+
+ _.each(abi.inputs, (input: DataItem) => {
+ this.params.push(DataTypeFactory.create(input, this));
+ });
+
+ // Compute signature
+ this.signature = `${this.name}(`;
+ _.each(this.params, (param: DataType, i: number) => {
+ this.signature += param.getSignature();
+ if (i < this.params.length - 1) {
+ this.signature += ',';
+ }
+ });
+ this.signature += ')';
+
+ // Compute selector
+ this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4)));
+
+ console.log(`--SIGNATURE--\n${this.signature}\n---------\n`);
+ console.log(`--SELECTOR--\n${this.selector}\n---------\n`);
+ }
+
+ public getSignature(): string {
+ return this.signature;
+ }
+
+ public assignValue(args: any[]) {
+ _.each(this.params, (param: DataType, i: number) => {
+ // Assign value to parameter
+ try {
+ param.assignValue(args[i]);
+ } catch (e) {
+ console.log('Failed to assign to ', param.getDataItem().name);
+ throw e;
+ }
+
+ if (param instanceof Pointer) {
+ this.children.push(param.getChildren()[0]);
+ }
+ });
+ }
+
+ public getHexValue(): string {
+ let value = "";
+ _.each(this.params, (param: DataType) => {
+ value += param.getHexValue();
+ });
+
+ return value;
+ }
+
+ public encode(args: any[]): string {
+ this.assignValue(args);
+ const calldata = new Calldata(this.selector, this.params.length);
+ this.bind(calldata, CalldataSection.PARAMS);
+
+ return calldata.getHexValue();
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+
+ public getHeaderSize(): BigNumber {
+ // Exclude selector
+ return new BigNumber(0);
+ }
+
+ public getBodySize(): BigNumber {
+ const nParams = new BigNumber(this.params.length);
+ const evmWordWidth = new BigNumber(32);
+ const size = nParams.times(evmWordWidth);
+ return size;
+ }
+
+ /*
+ encodeOptimized(args: any[]): string {
+ const calldata = new Memory();
+ // Assign values
+ optimizableParams : StaticDataType = [];
+ _.each(this.params, function(args: any[], i: number, param: DataType) {
+ param.assignValue(args[i]);
+ if (param instanceof DynamicDataType) {
+
+ }
+ });
+
+ // Find non-parameter leaves
+
+
+ return '';
+ } */
+
+ /*
+ decode(rawCalldata: string): any[] {
+ const calldata = new Calldata(this.name, this.params.length);
+ calldata.assignRaw(rawCalldata);
+ let args: any[];
+ let params = this.params;
+ _.each(params, function(args: any[], i: number, param: DataType) {
+ param.decodeFromCalldata(calldata);
+ args.push(param.getValue());
+ });
+
+ return args;
+ }*/
+} \ No newline at end of file
diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts
index b8f1d39ad..315537226 100644
--- a/packages/order-utils/test/abi_encoder_test.ts
+++ b/packages/order-utils/test/abi_encoder_test.ts
@@ -12,1294 +12,18 @@ import { MethodAbi, DataItem } from 'ethereum-types';
import { BigNumber } from '@0x/utils';
import { assert } from '@0x/order-utils/src/assert';
-
-const simpleAbi = {
- constant: false,
- inputs: [
- {
- name: 'greg',
- type: 'uint256',
- },
- {
- name: 'gregStr',
- type: 'string',
- },
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const stringAbi = {
- constant: false,
- inputs: [
- {
- name: 'greg',
- type: 'string[]',
- },
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const tupleAbi = {
- constant: false,
- inputs: [
- {
- components: [
- {
- name: 'someUint',
- type: 'uint256',
- },
- {
- name: 'someStr',
- type: 'string',
- },
- ],
- name: 'order',
- type: 'tuple',
- },
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const staticArrayAbi = {
- constant: false,
- inputs: [
- {
- name: 'someStaticArray',
- type: 'uint8[3]',
- }
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const crazyAbi = {
- constant: false,
- inputs: [
- /*{
- name: 'someUInt256',
- type: 'uint256',
- },
- {
- name: 'someInt256',
- type: 'int256',
- },
- {
- name: 'someInt32',
- type: 'int32',
- },
- {
- name: 'someByte',
- type: 'byte',
- },
- {
- name: 'someBytes32',
- type: 'bytes32',
- },
- {
- name: 'someBytes',
- type: 'bytes',
- },
- {
- name: 'someString',
- type: 'string',
- },*/
- /*{
- name: 'someAddress',
- type: 'address',
- },
- {
- name: 'someBool',
- type: 'bool',
- },*/
-
- {
- name: 'someStaticArray',
- type: 'uint8[3]',
- },
- {
- name: 'someStaticArrayWithDynamicMembers',
- type: 'string[2]',
- },
- {
- name: 'someDynamicArrayWithDynamicMembers',
- type: 'bytes[]',
- },
- {
- name: 'some2DArray',
- type: 'string[][]',
- },
- {
- name: 'someTuple',
- type: 'tuple',
- components: [
- {
- name: 'someUint32',
- type: 'uint32',
- },
- {
- name: 'someStr',
- type: 'string',
- },
- ],
- },
- {
- name: 'someTupleWithDynamicTypes',
- type: 'tuple',
- components: [
- {
- name: 'someUint',
- type: 'uint256',
- },
- {
- name: 'someStr',
- type: 'string',
- },
- //{
- // name: 'someStrArray',
- // type: 'string[]',
- /// },
- {
- name: 'someBytes',
- type: 'bytes',
- },
- {
- name: 'someAddress',
- type: 'address',
- },
- ],
- } /*,
- {
- name: 'someArrayOfTuplesWithDynamicTypes',
- type: 'tuple[]',
- components: [
- {
- name: 'someUint',
- type: 'uint256',
- },
- {
- name: 'someStr',
- type: 'string',
- },
- {
- name: 'someStrArray',
- type: 'string[]',
- },
- {
- name: 'someBytes',
- type: 'bytes',
- },
- {
- name: 'someAddress',
- type: 'address',
- },
- ],
- },*/,
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const simpleAbi2 = {
- constant: false,
- inputs: [
- {
- name: 'someByte',
- type: 'byte',
- },
- {
- name: 'someBytes32',
- type: 'bytes32',
- },
- {
- name: 'someBytes',
- type: 'bytes',
- },
- {
- name: 'someString',
- type: 'string',
- },
- ],
- name: 'simpleFunction',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
-
-const fillOrderAbi = {
- constant: false,
- inputs: [
- {
- components: [
- {
- name: 'makerAddress',
- type: 'address',
- },
- {
- name: 'takerAddress',
- type: 'address',
- },
- {
- name: 'feeRecipientAddress',
- type: 'address',
- },
- {
- name: 'senderAddress',
- type: 'address',
- },
- {
- name: 'makerAssetAmount',
- type: 'uint256',
- },
- {
- name: 'takerAssetAmount',
- type: 'uint256',
- },
- {
- name: 'makerFee',
- type: 'uint256',
- },
- {
- name: 'takerFee',
- type: 'uint256',
- },
- {
- name: 'expirationTimeSeconds',
- type: 'uint256',
- },
- {
- name: 'salt',
- type: 'uint256',
- },
- {
- name: 'makerAssetData',
- type: 'bytes',
- },
- {
- name: 'takerAssetData',
- type: 'bytes',
- },
- ],
- name: 'order',
- type: 'tuple',
- },
- {
- name: 'takerAssetFillAmount',
- type: 'uint256',
- },
- {
- name: 'salt',
- type: 'uint256',
- },
- {
- name: 'orderSignature',
- type: 'bytes',
- },
- {
- name: 'takerSignature',
- type: 'bytes',
- },
- ],
- name: 'fillOrder',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
-} as MethodAbi;
+import * as AbiEncoder from './abi_encoder';
+import * as AbiSamples from './abi_samples';
chaiSetup.configure();
const expect = chai.expect;
-namespace AbiEncoder {
- class Word {
- private value: string;
-
- constructor(value?: string) {
- if (value === undefined) {
- this.value = '';
- } else {
- this.value = value;
- }
- }
-
- public set(value: string) {
- if (value.length !== 64) {
- throw `Tried to create word that is not 32 bytes: ${value}`;
- }
-
- this.value = value;
- }
-
- public get(): string {
- return this.value;
- }
-
- public getAsHex(): string {
- return `0x${this.value}`;
- }
- }
-
- export enum CalldataSection {
- NONE,
- PARAMS,
- DATA,
- }
-
- class Memblock {
- private dataType: DataType;
- private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber };
-
- constructor(dataType: DataType) {
- this.dataType = dataType;
- this.location = {
- calldataSection: CalldataSection.NONE,
- sectionOffset: new BigNumber(0),
- offset: new BigNumber(0),
- };
- }
-
- public getSize(): BigNumber {
- return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength);
- }
-
- public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) {
- this.location.calldataSection = calldataSection;
- this.location.sectionOffset = sectionOffset;
- this.location.offset = offset;
- }
-
- public get(): string {
- return ethUtil.stripHexPrefix(this.dataType.getHexValue());
- }
-
- public getOffset(): BigNumber {
- return this.location.offset;
- }
-
- public getAbsoluteOffset(): BigNumber {
- return this.location.sectionOffset.plus(this.location.offset);
- }
-
- public getSection(): CalldataSection {
- return this.location.calldataSection;
- }
- }
-
- interface BindList {
- [key: string]: Memblock;
- }
-
- export class Calldata {
- private selector: string;
- private params: Memblock[];
- private data: Memblock[];
- private dataOffset: BigNumber;
- private currentDataOffset: BigNumber;
- private currentParamOffset: BigNumber;
- private bindList: BindList;
-
- constructor(selector: string, nParams: number) {
- this.selector = selector;
- console.log(this.selector);
- this.params = [];
- this.data = [];
- const evmWordSize = 32;
- this.dataOffset = new BigNumber(nParams).times(evmWordSize);
- this.currentDataOffset = new BigNumber(0);
- this.currentParamOffset = new BigNumber(0);
- this.bindList = {};
- }
-
- public bind(dataType: DataType, section: CalldataSection) {
- if (dataType.getId() in this.bindList) {
- throw `Rebind on ${dataType.getId()}`;
- }
- const memblock = new Memblock(dataType);
- switch (section) {
- case CalldataSection.PARAMS:
- this.params.push(memblock);
- memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset);
-
- console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`);
- this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize());
- break;
-
- case CalldataSection.DATA:
- this.data.push(memblock);
- memblock.assignLocation(section, this.dataOffset, this.currentDataOffset);
-
- console.log(
- `Binding ${dataType.getDataItem().name} to DATA at ${memblock
- .getAbsoluteOffset()
- .toString(16)}`,
- );
- this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize());
- break;
-
- default:
- throw `Unrecognized calldata section: ${section}`;
- }
-
- this.bindList[dataType.getId()] = memblock;
- dataType.rbind(memblock);
- }
-
- public getHexValue(): string {
- let hexValue = `${this.selector}`;
- _.each(this.params, (memblock: Memblock) => {
- hexValue += memblock.get();
- });
- _.each(this.data, (memblock: Memblock) => {
- hexValue += memblock.get();
- });
-
- return hexValue;
- }
- }
-
- export abstract class DataType {
- private dataItem: DataItem;
- private hexValue: string;
- protected memblock: Memblock | undefined;
- protected children: DataType[];
-
- constructor(dataItem: DataItem) {
- this.dataItem = dataItem;
- this.hexValue = '0x';
- this.memblock = undefined;
- this.children = [];
- }
-
- protected assignHexValue(hexValue: string) {
- this.hexValue = hexValue;
- }
-
- public getHexValue(): string {
- return this.hexValue;
- }
-
- public getDataItem(): DataItem {
- return this.dataItem;
- }
-
- public rbind(memblock: Memblock) {
- this.memblock = memblock;
- }
-
- public bind(calldata: Calldata, section: CalldataSection) {
- calldata.bind(this, section);
- }
-
- public getId(): string {
- return this.dataItem.name;
- }
-
- public getOffset(): BigNumber {
- if (this.memblock === undefined) return new BigNumber(0);
- return this.memblock.getOffset();
- }
-
- public getAbsoluteOffset(): BigNumber {
- if (this.memblock === undefined) return new BigNumber(0);
- return this.memblock.getAbsoluteOffset();
- }
-
- public getSize(): BigNumber {
- if (this.memblock === undefined) return new BigNumber(0);
- return this.memblock.getSize();
- }
-
- public getChildren(): DataType[] {
- return this.children;
- }
-
- public abstract assignValue(value: any): void;
- public abstract getSignature(): string;
- public abstract isStatic(): boolean;
- }
-
- export abstract class StaticDataType extends DataType {
- constructor(dataItem: DataItem) {
- super(dataItem);
- }
- }
-
- export abstract class DynamicDataType extends DataType {
- constructor(dataItem: DataItem) {
- super(dataItem);
- }
- }
-
- export class Address extends StaticDataType {
- constructor(dataItem: DataItem) {
- super(dataItem);
- expect(Address.matchGrammar(dataItem.type)).to.be.true();
- }
-
- public assignValue(value: string) {
- const evmWordWidth = 32;
- const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth));
- this.assignHexValue(hexValue);
- }
-
- public getSignature(): string {
- return `address`;
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- public static matchGrammar(type: string): boolean {
- return type === 'address';
- }
- }
-
- export class Bool extends StaticDataType {
- constructor(dataItem: DataItem) {
- super(dataItem);
- expect(Bool.matchGrammar(dataItem.type)).to.be.true();
- }
-
- public assignValue(value: boolean) {
- const evmWordWidth = 32;
- const encodedValue = value === true ? '0x1' : '0x0';
- const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth));
- this.assignHexValue(hexValue);
- }
-
- public getSignature(): string {
- return 'bool';
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- public static matchGrammar(type: string): boolean {
- return type === 'bool';
- }
- }
-
- abstract class Number extends StaticDataType {
- static MAX_WIDTH: number = 256;
- static DEFAULT_WIDTH: number = Number.MAX_WIDTH;
- width: number = Number.DEFAULT_WIDTH;
-
- constructor(dataItem: DataItem, matcher: RegExp) {
- super(dataItem);
- const matches = matcher.exec(dataItem.type);
- expect(matches).to.be.not.null();
- if (matches !== null && matches.length === 2 && matches[1] !== undefined) {
- this.width = parseInt(matches[1]);
- } else {
- this.width = 256;
- }
- }
-
- public assignValue(value: BigNumber) {
- if (value.greaterThan(this.getMaxValue())) {
- throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`;
- } else if (value.lessThan(this.getMinValue())) {
- throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`;
- }
-
- const hexBase = 16;
- const evmWordWidth = 32;
- let valueBuf: Buffer;
- if (value.greaterThanOrEqualTo(0)) {
- valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth);
- } else {
- // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves.
- // Step 1/3: Convert value to positive binary string
- const binBase = 2;
- const valueBin = value.times(-1).toString(binBase);
-
- // Step 2/3: Invert binary value
- const bitsInEvmWord = 256;
- let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length);
- _.each(valueBin, (bit: string) => {
- invertedValueBin += bit === '1' ? '0' : '1';
- });
- const invertedValue = new BigNumber(invertedValueBin, binBase);
-
- // Step 3/3: Add 1 to inverted value
- // The result is the two's-complement represent of the input value.
- const negativeValue = invertedValue.plus(1);
-
- // Convert the negated value to a hex string
- valueBuf = ethUtil.setLengthLeft(
- ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`),
- evmWordWidth,
- );
- }
-
- const encodedValue = ethUtil.bufferToHex(valueBuf);
- this.assignHexValue(encodedValue);
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- public abstract getMaxValue(): BigNumber;
- public abstract getMinValue(): BigNumber;
- }
-
- export class Int extends Number {
- static matcher = RegExp(
- '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
- );
-
- constructor(dataItem: DataItem) {
- super(dataItem, Int.matcher);
- }
-
- public getMaxValue(): BigNumber {
- return new BigNumber(2).toPower(this.width - 1).sub(1);
- }
-
- public getMinValue(): BigNumber {
- return new BigNumber(2).toPower(this.width - 1).times(-1);
- }
-
- public getSignature(): string {
- return `int${this.width}`;
- }
-
- public static matchGrammar(type: string): boolean {
- return this.matcher.test(type);
- }
- }
-
- export class UInt extends Number {
- static matcher = RegExp(
- '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
- );
-
- constructor(dataItem: DataItem) {
- super(dataItem, UInt.matcher);
- }
-
- public getMaxValue(): BigNumber {
- return new BigNumber(2).toPower(this.width).sub(1);
- }
-
- public getMinValue(): BigNumber {
- return new BigNumber(0);
- }
-
- public getSignature(): string {
- return `uint${this.width}`;
- }
-
- public static matchGrammar(type: string): boolean {
- return this.matcher.test(type);
- }
- }
-
- export class Byte extends StaticDataType {
- static matcher = RegExp(
- '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$',
- );
-
- static DEFAULT_WIDTH = 1;
- width: number = Byte.DEFAULT_WIDTH;
-
- constructor(dataItem: DataItem) {
- super(dataItem);
- const matches = Byte.matcher.exec(dataItem.type);
- expect(matches).to.be.not.null();
- if (matches !== null && matches.length === 3 && matches[2] !== undefined) {
- this.width = parseInt(matches[2]);
- } else {
- this.width = Byte.DEFAULT_WIDTH;
- }
- }
-
- public assignValue(value: string | Buffer) {
- // Convert value into a buffer and do bounds checking
- const valueBuf = ethUtil.toBuffer(value);
- if (valueBuf.byteLength > this.width) {
- throw new Error(
- `Tried to assign ${value} (${
- valueBuf.byteLength
- } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`,
- );
- } else if (value.length % 2 !== 0) {
- throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
- }
-
- // Store value as hex
- const evmWordWidth = 32;
- const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth);
- const hexValue = ethUtil.bufferToHex(paddedValue);
- this.assignHexValue(hexValue);
- }
-
- public getSignature(): string {
- // Note that `byte` reduces to `bytes1`
- return `bytes${this.width}`;
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- public static matchGrammar(type: string): boolean {
- return this.matcher.test(type);
- }
- }
-
- export class Bytes extends DynamicDataType {
- static UNDEFINED_LENGTH = new BigNumber(-1);
- length: BigNumber = Bytes.UNDEFINED_LENGTH;
-
- constructor(dataItem: DataItem) {
- super(dataItem);
- expect(Bytes.matchGrammar(dataItem.type)).to.be.true();
- }
-
- public assignValue(value: string | Buffer) {
- if (typeof value === 'string' && !value.startsWith('0x')) {
- throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`);
- }
- const valueBuf = ethUtil.toBuffer(value);
- if (value.length % 2 !== 0) {
- throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
- }
-
- const wordsForValue = Math.ceil(valueBuf.byteLength / 32);
- const paddedBytesForValue = wordsForValue * 32;
- const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
- const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32);
- const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
- const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
-
- this.assignHexValue(encodedValue);
- }
-
- public getSignature(): string {
- return 'bytes';
- }
-
- public isStatic(): boolean {
- return false;
- }
-
- public static matchGrammar(type: string): boolean {
- return type === 'bytes';
- }
- }
-
- export class SolString extends DynamicDataType {
- constructor(dataItem: DataItem) {
- super(dataItem);
- expect(SolString.matchGrammar(dataItem.type)).to.be.true();
- }
-
- public assignValue(value: string) {
- const wordsForValue = Math.ceil(value.length / 32);
- const paddedBytesForValue = wordsForValue * 32;
- const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
- const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32);
- const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]);
- const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
-
- this.assignHexValue(encodedValue);
- }
-
- public getSignature(): string {
- return 'string';
- }
-
- public isStatic(): boolean {
- return false;
- }
-
- public static matchGrammar(type: string): boolean {
- return type === 'string';
- }
- }
-
- export class SolArray extends DynamicDataType {
- static matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
- static UNDEFINED_LENGTH = new BigNumber(-1);
- length: BigNumber = SolArray.UNDEFINED_LENGTH;
- type: string = '[undefined]';
- isLengthDefined: boolean;
-
- constructor(dataItem: DataItem) {
- super(dataItem);
- const matches = SolArray.matcher.exec(dataItem.type);
- expect(matches).to.be.not.null();
- console.log(JSON.stringify(matches));
- if (matches === null || matches.length !== 3) {
- throw new Error(`Could not parse array: ${dataItem.type}`);
- } else if (matches[1] === undefined) {
- throw new Error(`Could not parse array type: ${dataItem.type}`);
- } else if (matches[2] === undefined) {
- throw new Error(`Could not parse array length: ${dataItem.type}`);
- }
-
- // Check if length is undefined
- if (matches[2] === '') {
- this.type = matches[1];
- this.length = SolArray.UNDEFINED_LENGTH;
- this.isLengthDefined = false;
- return;
- }
-
- // Parse out array type/length and construct children
- this.isLengthDefined = true;
- this.type = matches[1];
- this.length = new BigNumber(matches[2], 10);
- if (this.length.lessThan(0)) {
- throw new Error(`Bad array length: ${JSON.stringify(this.length)}`);
- }
- this.constructChildren();
- }
-
- private constructChildren() {
- for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
- const childDataItem = {
- type: this.type,
- name: `${this.getDataItem().name}[${idx.toString(10)}]`,
- } as DataItem;
- const child = DataTypeFactory.create(childDataItem, this);
- this.children.push(child);
- }
- }
-
- // @TODO: HACKY -- shouldn't really have children for a
- public getChildren(): DataType[] {
- if (this.isStatic()) {
- return [];
- } else {
- return this.children;
- }
- }
-
- public assignValue(value: any[]) {
- // Sanity check length
- const valueLength = new BigNumber(value.length);
- if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) {
- throw new Error(
- `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify(
- valueLength,
- )}`,
- );
- }
-
- // Assign length if not already set
- if (this.length === SolArray.UNDEFINED_LENGTH) {
- this.length = valueLength;
- this.constructChildren();
- }
-
- // Assign values to children
- for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
- const idxNumber = idx.toNumber();
- this.children[idxNumber].assignValue(value[idxNumber]);
- }
- }
-
- private getHexValueDynamicArray(): string {
- const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`);
- const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32);
- let valueBuf = lengthBuf;
-
- const valueHex = ethUtil.bufferToHex(valueBuf);
- return valueHex;
- }
-
- private getHexValueStaticArray(): string {
- let valueBuf = new Buffer("");
-
- for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
- const idxNumber = idx.toNumber();
- const childValueHex = this.children[idxNumber].getHexValue();
- const childValueBuf = ethUtil.toBuffer(childValueHex);
- valueBuf = Buffer.concat([valueBuf, childValueBuf]);
-
- console.log(JSON.stringify(idx));
- }
-
- // Convert value buffer to hex
- const valueHex = ethUtil.bufferToHex(valueBuf);
- return valueHex;
- }
-
- public getHexValue(): string {
- if (this.isLengthDefined) {
- return this.getHexValueStaticArray();
- } else {
- return this.getHexValueDynamicArray();
- }
- }
-
- public isStatic(): boolean {
- return this.isLengthDefined;
- }
-
- public static matchGrammar(type: string): boolean {
- return this.matcher.test(type);
- }
-
- public getSignature(): string {
- let type = this.type;
- if (this.type === 'tuple') {
- let tupleDataItem = {
- type: 'tuple',
- name: 'N/A',
- } as DataItem;
- const tupleComponents = this.getDataItem().components;
- if (tupleComponents !== undefined) {
- tupleDataItem.components = tupleComponents;
- }
- const tuple = new Tuple(tupleDataItem);
- type = tuple.getSignature();
- }
-
- if (this.length.equals(SolArray.UNDEFINED_LENGTH)) {
- return `${type}[]`;
- }
- return `${type}[${this.length}]`;
- }
- }
-
- export class Tuple extends DynamicDataType {
- private length: BigNumber;
- private childMap: { [key: string]: number };
-
- constructor(dataItem: DataItem) {
- super(dataItem);
- expect(Tuple.matchGrammar(dataItem.type)).to.be.true();
- this.length = new BigNumber(0);
- this.childMap = {};
- if (dataItem.components !== undefined) {
- this.constructChildren(dataItem.components);
- this.length = new BigNumber(dataItem.components.length);
- } else {
- throw new Error('Components undefined');
- }
- }
-
- private constructChildren(dataItems: DataItem[]) {
- _.each(dataItems, (dataItem: DataItem) => {
- const childDataItem = {
- type: dataItem.type,
- name: `${this.getDataItem().name}.${dataItem.name}`,
- } as DataItem;
- const child = DataTypeFactory.create(childDataItem, this);
- this.childMap[dataItem.name] = this.children.length;
- this.children.push(child);
- });
- }
-
- private assignValueFromArray(value: any[]) {
- // Sanity check length
- const valueLength = new BigNumber(value.length);
- if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) {
- throw new Error(
- `Expected array of ${JSON.stringify(
- this.length,
- )} elements, but got array of length ${JSON.stringify(valueLength)}`,
- );
- }
-
- // Assign values to children
- for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
- const idxNumber = idx.toNumber();
- this.children[idxNumber].assignValue(value[idxNumber]);
- }
- }
-
- private assignValueFromObject(obj: object) {
- let childMap = _.cloneDeep(this.childMap);
- _.forOwn(obj, (value: any, key: string) => {
- if (key in childMap === false) {
- throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`);
- }
- this.children[this.childMap[key]].assignValue(value);
- delete childMap[key];
- });
-
- if (Object.keys(childMap).length !== 0) {
- throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`);
- }
- }
-
- public assignValue(value: any[] | object) {
- if (value instanceof Array) {
- this.assignValueFromArray(value);
- } else if (typeof value === 'object') {
- this.assignValueFromObject(value);
- } else {
- throw new Error(`Unexpected type for ${value}`);
- }
- }
-
- public getHexValue(): string {
- return '0x';
- }
-
- public getSignature(): string {
- // Compute signature
- let signature = `(`;
- _.each(this.children, (child: DataType, i: number) => {
- signature += child.getSignature();
- if (i < this.children.length - 1) {
- signature += ',';
- }
- });
- signature += ')';
- return signature;
- }
-
- public isStatic(): boolean {
- return false; // @TODO: True in every case or only when dynamic data?
- }
-
- public static matchGrammar(type: string): boolean {
- return type === 'tuple';
- }
- }
-
- /* TODO
- class Fixed extends StaticDataType {}
-
- class UFixed extends StaticDataType {}*/
-
- export class Pointer extends StaticDataType {
- destDataType: DynamicDataType;
- parentDataType: DataType;
-
- constructor(destDataType: DynamicDataType, parentDataType: DataType) {
- const destDataItem = destDataType.getDataItem();
- const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem;
- super(dataItem);
- this.destDataType = destDataType;
- this.parentDataType = parentDataType;
- this.children.push(destDataType);
- }
-
- /*
- public assignValue(destDataType: DynamicDataType) {
- this.destDataType = destDataType;
- }*/
-
- public assignValue(value: any) {
- this.destDataType.assignValue(value);
- }
-
- public getHexValue(): string {
- console.log(
- '*'.repeat(40),
- this.destDataType.getAbsoluteOffset().toString(16),
- '^'.repeat(150),
- this.parentDataType.getAbsoluteOffset().toString(16),
- );
-
- let offset = this.destDataType
- .getAbsoluteOffset()
- .minus(this.parentDataType.getAbsoluteOffset().plus(this.parentDataType.getSize()));
- const hexBase = 16;
- const evmWordWidth = 32;
- const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth);
- const encodedValue = ethUtil.bufferToHex(valueBuf);
- return encodedValue;
- }
-
- public getSignature(): string {
- return this.destDataType.getSignature();
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- public encodeToCalldata(calldata: Calldata): void {
- throw 2;
- }
- }
-
- export class DataTypeFactory {
- public static mapDataItemToDataType(dataItem: DataItem): DataType {
- console.log(`Type: ${dataItem.type}`);
-
- if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
- if (Address.matchGrammar(dataItem.type)) return new Address(dataItem);
- if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem);
- if (Int.matchGrammar(dataItem.type)) return new Int(dataItem);
- if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem);
- if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem);
- if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem);
- if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
- if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem);
- if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem);
- //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem);
- //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem);
-
- throw new Error(`Unrecognized data type: '${dataItem.type}'`);
- }
-
- public static create(dataItem: DataItem, parentDataType: DataType): DataType {
- const dataType = DataTypeFactory.mapDataItemToDataType(dataItem);
- if (dataType.isStatic()) {
- return dataType;
- } else {
- const pointer = new Pointer(dataType, parentDataType);
- return pointer;
- }
-
- throw new Error(`Unrecognized instance type: '${dataType}'`);
- }
- }
-
- class Queue<T> {
- private store: T[] = [];
- push(val: T) {
- this.store.push(val);
- }
- pop(): T | undefined {
- return this.store.shift();
- }
- }
-
- export class Method extends DataType {
- name: string;
- params: DataType[];
- private signature: string;
- selector: string;
-
- constructor(abi: MethodAbi) {
- super({ type: 'method', name: abi.name });
- this.name = abi.name;
- this.params = [];
-
- _.each(abi.inputs, (input: DataItem) => {
- this.params.push(DataTypeFactory.create(input, this));
- });
-
- // Compute signature
- this.signature = `${this.name}(`;
- _.each(this.params, (param: DataType, i: number) => {
- this.signature += param.getSignature();
- if (i < this.params.length - 1) {
- this.signature += ',';
- }
- });
- this.signature += ')';
-
- // Compute selector
- this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4)));
-
- console.log(`--SIGNATURE--\n${this.signature}\n---------\n`);
- console.log(`--SELECTOR--\n${this.selector}\n---------\n`);
- }
-
- public getSignature(): string {
- return this.signature;
- }
-
- public assignValue(args: any[]) {
- const calldata = new Calldata(this.selector, this.params.length);
- const params = this.params;
- const paramQueue = new Queue<DataType>();
- _.each(params, (param: DataType, i: number) => {
- try {
- param.assignValue(args[i]);
- } catch (e) {
- console.log('Failed to assign to ', param.getDataItem().name);
- throw e;
- }
- param.bind(calldata, CalldataSection.PARAMS);
- _.each(param.getChildren(), (child: DataType) => {
- paramQueue.push(child);
- });
- });
-
- let param: DataType | undefined = undefined;
- while ((param = paramQueue.pop()) !== undefined) {
- param.bind(calldata, CalldataSection.DATA);
- _.each(param.getChildren(), (child: DataType) => {
- paramQueue.push(child);
- });
- }
-
- console.log(calldata);
-
- this.assignHexValue(calldata.getHexValue());
-
- //return calldata.getRaw();
- }
-
- public encode(args: any[]): string {
- this.assignValue(args);
- return this.getHexValue();
- }
-
- public isStatic(): boolean {
- return true;
- }
-
- /*
- encodeOptimized(args: any[]): string {
- const calldata = new Memory();
- // Assign values
- optimizableParams : StaticDataType = [];
- _.each(this.params, function(args: any[], i: number, param: DataType) {
- param.assignValue(args[i]);
- if (param instanceof DynamicDataType) {
-
- }
- });
-
- // Find non-parameter leaves
-
-
- return '';
- } */
-
- /*
- decode(rawCalldata: string): any[] {
- const calldata = new Calldata(this.name, this.params.length);
- calldata.assignRaw(rawCalldata);
- let args: any[];
- let params = this.params;
- _.each(params, function(args: any[], i: number, param: DataType) {
- param.decodeFromCalldata(calldata);
- args.push(param.getValue());
- });
-
- return args;
- }*/
- }
-}
-
describe.only('ABI Encoder', () => {
describe.only('Just a Greg, Eh', () => {
+
+
it('Crazy ABI', async () => {
- const method = new AbiEncoder.Method(crazyAbi);
+ const method = new AbiEncoder.Method(AbiSamples.crazyAbi);
console.log(method.getSignature());
const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)],
@@ -1405,20 +129,50 @@ describe.only('ABI Encoder', () => {
expect(calldata).to.be.equal(expectedCalldata);*/
});
- it.only('Static Array ABI', async () => {
- const method = new AbiEncoder.Method(staticArrayAbi);
- const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]];
+ it.only('Fixed Lenfgth Array / Dynamic Members', async () => {
+ const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi);
+ const args = [["Brave", "New", "World"]];
const calldata = method.encode(args);
console.log(calldata);
console.log('*'.repeat(40));
console.log(JSON.stringify(args));
const expectedCalldata =
+ '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ });
+
+ it.only('Unfixed Length Array / Dynamic Members ABI', async () => {
+ const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi);
+ const args = [["Brave", "New", "World"]];
+ const calldata = method.encode(args);
+ console.log(calldata);
+ console.log('*'.repeat(40));
+ console.log(JSON.stringify(args));
+ const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ });
+
+ it.only('Unfixed Length Array / Static Members ABI', async () => {
+ const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi);
+ const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]];
+ const calldata = method.encode(args);
+ const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036';
+ expect(calldata).to.be.equal(expectedCalldata);
+ });
+
+
+ it.only('Fixed Length Array / Static Members ABI', async () => {
+ const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi);
+ const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]];
+ const calldata = method.encode(args);
+ const expectedCalldata =
'0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036';
expect(calldata).to.be.equal(expectedCalldata);
});
+
it('Simple ABI 2', async () => {
- const method = new AbiEncoder.Method(simpleAbi2);
+ const method = new AbiEncoder.Method(AbiSamples.simpleAbi2);
const args = [
'0xaf', // e (bytes1)
@@ -1434,14 +188,14 @@ describe.only('ABI Encoder', () => {
});
it('Yessir', async () => {
- const method = new AbiEncoder.Method(simpleAbi);
+ const method = new AbiEncoder.Method(AbiSamples.simpleAbi);
const calldata = method.encode([new BigNumber(5), 'five']);
console.log(calldata);
expect(true).to.be.true();
});
it('Array ABI', async () => {
- const method = new AbiEncoder.Method(stringAbi);
+ const method = new AbiEncoder.Method(AbiSamples.stringAbi);
const calldata = method.encode([['five', 'six', 'seven']]);
console.log(method.getSignature());
console.log(method.selector);
@@ -1453,7 +207,7 @@ describe.only('ABI Encoder', () => {
});
it('Object ABI (Array input)', async () => {
- const method = new AbiEncoder.Method(tupleAbi);
+ const method = new AbiEncoder.Method(AbiSamples.tupleAbi);
const calldata = method.encode([[new BigNumber(5), 'five']]);
console.log(method.getSignature());
console.log(method.selector);
@@ -1465,7 +219,7 @@ describe.only('ABI Encoder', () => {
});
it('Object ABI (Object input)', async () => {
- const method = new AbiEncoder.Method(tupleAbi);
+ const method = new AbiEncoder.Method(AbiSamples.tupleAbi);
const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]);
console.log(method.getSignature());
console.log(method.selector);
@@ -1477,7 +231,7 @@ describe.only('ABI Encoder', () => {
});
it.skip('Object ABI (Object input - Missing Key)', async () => {
- const method = new AbiEncoder.Method(tupleAbi);
+ const method = new AbiEncoder.Method(AbiSamples.tupleAbi);
const calldata = method.encode([{ someUint: new BigNumber(5) }]);
console.log(method.getSignature());
console.log(method.selector);
@@ -1491,7 +245,7 @@ describe.only('ABI Encoder', () => {
});
it.skip('Object ABI (Object input - Too Many Keys)', async () => {
- const method = new AbiEncoder.Method(tupleAbi);
+ const method = new AbiEncoder.Method(AbiSamples.tupleAbi);
const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]);
console.log(method.getSignature());
console.log(method.selector);
diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts
new file mode 100644
index 000000000..2e7111504
--- /dev/null
+++ b/packages/order-utils/test/abi_samples.ts
@@ -0,0 +1,358 @@
+import { MethodAbi } from 'ethereum-types';
+
+export const simpleAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'greg',
+ type: 'uint256',
+ },
+ {
+ name: 'gregStr',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const stringAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'greg',
+ type: 'string[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const tupleAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const staticArrayAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'uint8[3]',
+ }
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const staticArrayDynamicMembersAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'string[3]',
+ }
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const dynamicArrayDynamicMembersAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'string[]',
+ }
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const dynamicArrayStaticMembersAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'uint8[]',
+ }
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const crazyAbi = {
+ constant: false,
+ inputs: [
+ /*{
+ name: 'someUInt256',
+ type: 'uint256',
+ },
+ {
+ name: 'someInt256',
+ type: 'int256',
+ },
+ {
+ name: 'someInt32',
+ type: 'int32',
+ },
+ {
+ name: 'someByte',
+ type: 'byte',
+ },
+ {
+ name: 'someBytes32',
+ type: 'bytes32',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },*/
+ /*{
+ name: 'someAddress',
+ type: 'address',
+ },
+ {
+ name: 'someBool',
+ type: 'bool',
+ },*/
+
+ {
+ name: 'someStaticArray',
+ type: 'uint8[3]',
+ },
+ {
+ name: 'someStaticArrayWithDynamicMembers',
+ type: 'string[2]',
+ },
+ {
+ name: 'someDynamicArrayWithDynamicMembers',
+ type: 'bytes[]',
+ },
+ {
+ name: 'some2DArray',
+ type: 'string[][]',
+ },
+ {
+ name: 'someTuple',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint32',
+ type: 'uint32',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ ],
+ },
+ {
+ name: 'someTupleWithDynamicTypes',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ //{
+ // name: 'someStrArray',
+ // type: 'string[]',
+ /// },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ } /*,
+ {
+ name: 'someArrayOfTuplesWithDynamicTypes',
+ type: 'tuple[]',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ {
+ name: 'someStrArray',
+ type: 'string[]',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },*/,
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const simpleAbi2 = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someByte',
+ type: 'byte',
+ },
+ {
+ name: 'someBytes32',
+ type: 'bytes32',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;
+
+export const fillOrderAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'makerAddress',
+ type: 'address',
+ },
+ {
+ name: 'takerAddress',
+ type: 'address',
+ },
+ {
+ name: 'feeRecipientAddress',
+ type: 'address',
+ },
+ {
+ name: 'senderAddress',
+ type: 'address',
+ },
+ {
+ name: 'makerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'takerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'makerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'takerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'expirationTimeSeconds',
+ type: 'uint256',
+ },
+ {
+ name: 'salt',
+ type: 'uint256',
+ },
+ {
+ name: 'makerAssetData',
+ type: 'bytes',
+ },
+ {
+ name: 'takerAssetData',
+ type: 'bytes',
+ },
+ ],
+ name: 'order',
+ type: 'tuple',
+ },
+ {
+ name: 'takerAssetFillAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'salt',
+ type: 'uint256',
+ },
+ {
+ name: 'orderSignature',
+ type: 'bytes',
+ },
+ {
+ name: 'takerSignature',
+ type: 'bytes',
+ },
+ ],
+ name: 'fillOrder',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+} as MethodAbi;