Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
This commit is contained in:
parent
19cda0eb03
commit
6a5965d73b
@ -48,8 +48,9 @@ describe('ZeroEx library', () => {
|
|||||||
const ethSignSignature =
|
const ethSignSignature =
|
||||||
'0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
|
'0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
|
||||||
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||||
|
const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
it("should return false if the data doesn't pertain to the signature & address", async () => {
|
it("should return false if the data doesn't pertain to the signature & address", async () => {
|
||||||
return expect((zeroEx.exchange as any).isValidSignatureAsync('0x0', address, ethSignSignature)).to.become(
|
return expect((zeroEx.exchange as any).isValidSignatureAsync(bytes32Zeros, address, ethSignSignature)).to.become(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -82,6 +82,23 @@ export class BaseContract {
|
|||||||
}
|
}
|
||||||
return txDataWithDefaults;
|
return txDataWithDefaults;
|
||||||
}
|
}
|
||||||
|
// Throws if the given arguments cannot be safely/correctly encoded based on
|
||||||
|
// the given inputAbi. An argument may not be considered safely encodeable
|
||||||
|
// if it overflows the corresponding Solidity type, there is a bug in the
|
||||||
|
// encoder, or the encoder performs unsafe type coercion.
|
||||||
|
public static strictArgumentEncodingCheck(inputAbi: DataItem[], args: any[]): void {
|
||||||
|
const coder = (ethers as any).utils.AbiCoder.defaultCoder;
|
||||||
|
const params = abiUtils.parseEthersParams(inputAbi);
|
||||||
|
const rawEncoded = coder.encode(params.names, params.types, args);
|
||||||
|
const rawDecoded = coder.decode(params.names, params.types, rawEncoded);
|
||||||
|
for (let i = 0; i < rawDecoded.length; i++) {
|
||||||
|
const original = args[i];
|
||||||
|
const decoded = rawDecoded[i];
|
||||||
|
if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) {
|
||||||
|
throw new Error(`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${params.types[i]}. (Possible type overflow or other encoding error)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
protected _lookupEthersInterface(functionSignature: string): ethers.Interface {
|
protected _lookupEthersInterface(functionSignature: string): ethers.Interface {
|
||||||
const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature];
|
const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature];
|
||||||
if (_.isUndefined(ethersInterface)) {
|
if (_.isUndefined(ethersInterface)) {
|
||||||
|
110
packages/base-contract/test/base_contract_test.ts
Normal file
110
packages/base-contract/test/base_contract_test.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { BaseContract } from '../src';
|
||||||
|
|
||||||
|
const { expect } = chai;
|
||||||
|
|
||||||
|
describe('BaseContract', () => {
|
||||||
|
describe('strictArgumentEncodingCheck', () => {
|
||||||
|
it('works for simple types', () => {
|
||||||
|
BaseContract.strictArgumentEncodingCheck([{ name: 'to', type: 'address' }], ['0xe834ec434daba538cd1b9fe1582052b880bd7e63']);
|
||||||
|
});
|
||||||
|
it('works for array types', () => {
|
||||||
|
const inputAbi = [{
|
||||||
|
name: 'takerAssetFillAmounts',
|
||||||
|
type: 'uint256[]',
|
||||||
|
}];
|
||||||
|
const args = [
|
||||||
|
[
|
||||||
|
'9000000000000000000',
|
||||||
|
'79000000000000000000',
|
||||||
|
'979000000000000000000',
|
||||||
|
'7979000000000000000000',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
BaseContract.strictArgumentEncodingCheck(inputAbi, args);
|
||||||
|
});
|
||||||
|
it('works for tuple/struct types', () => {
|
||||||
|
const inputAbi = [
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const args = [
|
||||||
|
{
|
||||||
|
makerAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb',
|
||||||
|
takerAddress: '0x0000000000000000000000000000000000000000',
|
||||||
|
feeRecipientAddress: '0xe834ec434daba538cd1b9fe1582052b880bd7e63',
|
||||||
|
senderAddress: '0x0000000000000000000000000000000000000000',
|
||||||
|
makerAssetAmount: '0',
|
||||||
|
takerAssetAmount: '200000000000000000000',
|
||||||
|
makerFee: '1000000000000000000',
|
||||||
|
takerFee: '1000000000000000000',
|
||||||
|
expirationTimeSeconds: '1532563026',
|
||||||
|
salt: '59342956082154660870994022243365949771115859664887449740907298019908621891376',
|
||||||
|
makerAssetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||||
|
takerAssetData: '0xf47261b00000000000000000000000001d7022f5b17d2f8b695918fb48fa1089c9f85401',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
BaseContract.strictArgumentEncodingCheck(inputAbi, args);
|
||||||
|
});
|
||||||
|
it('throws for integer overflows', () => {
|
||||||
|
expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'amount', type: 'uint8' }], ['256'])).to.throw();
|
||||||
|
});
|
||||||
|
it('throws for fixed byte array overflows', () => {
|
||||||
|
expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'hash', type: 'bytes8' }], ['0x001122334455667788'])).to.throw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -7,6 +7,7 @@ async callAsync(
|
|||||||
const functionSignature = '{{this.functionSignature}}';
|
const functionSignature = '{{this.functionSignature}}';
|
||||||
const inputAbi = self._lookupAbi(functionSignature).inputs;
|
const inputAbi = self._lookupAbi(functionSignature).inputs;
|
||||||
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
|
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
|
||||||
|
BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]);
|
||||||
const ethersFunction = self._lookupEthersInterface(functionSignature).functions.{{this.name}}(
|
const ethersFunction = self._lookupEthersInterface(functionSignature).functions.{{this.name}}(
|
||||||
{{> params inputs=inputs}}
|
{{> params inputs=inputs}}
|
||||||
) as ethers.CallDescription;
|
) as ethers.CallDescription;
|
||||||
|
@ -11,6 +11,7 @@ public {{this.tsName}} = {
|
|||||||
const self = this as any as {{contractName}}Contract;
|
const self = this as any as {{contractName}}Contract;
|
||||||
const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs;
|
const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs;
|
||||||
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
|
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
|
||||||
|
BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]);
|
||||||
const encodedData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}(
|
const encodedData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}(
|
||||||
{{> params inputs=inputs}}
|
{{> params inputs=inputs}}
|
||||||
).data;
|
).data;
|
||||||
|
@ -113,7 +113,7 @@ describe('MixinSignatureValidator', () => {
|
|||||||
|
|
||||||
it('should revert when signature type is unsupported', async () => {
|
it('should revert when signature type is unsupported', async () => {
|
||||||
const unsupportedSignatureType = SignatureType.NSignatureTypes;
|
const unsupportedSignatureType = SignatureType.NSignatureTypes;
|
||||||
const unsupportedSignatureHex = `0x${unsupportedSignatureType}`;
|
const unsupportedSignatureHex = '0x' + Buffer.from([unsupportedSignatureType]).toString('hex');
|
||||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
return expectContractCallFailed(
|
return expectContractCallFailed(
|
||||||
signatureValidator.publicIsValidSignature.callAsync(
|
signatureValidator.publicIsValidSignature.callAsync(
|
||||||
@ -126,7 +126,7 @@ describe('MixinSignatureValidator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should revert when SignatureType=Illegal', async () => {
|
it('should revert when SignatureType=Illegal', async () => {
|
||||||
const unsupportedSignatureHex = `0x${SignatureType.Illegal}`;
|
const unsupportedSignatureHex = '0x' + Buffer.from([SignatureType.Illegal]).toString('hex');
|
||||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
return expectContractCallFailed(
|
return expectContractCallFailed(
|
||||||
signatureValidator.publicIsValidSignature.callAsync(
|
signatureValidator.publicIsValidSignature.callAsync(
|
||||||
@ -139,7 +139,7 @@ describe('MixinSignatureValidator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
|
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
|
||||||
const signatureHex = `0x${SignatureType.Invalid}`;
|
const signatureHex = '0x' + Buffer.from([SignatureType.Invalid]).toString('hex');
|
||||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||||
orderHashHex,
|
orderHashHex,
|
||||||
|
@ -22,7 +22,8 @@ describe('Signature utils', () => {
|
|||||||
let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||||
|
|
||||||
it("should return false if the data doesn't pertain to the signature & address", async () => {
|
it("should return false if the data doesn't pertain to the signature & address", async () => {
|
||||||
expect(await isValidSignatureAsync(provider, '0x0', ethSignSignature, address)).to.be.false();
|
const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
expect(await isValidSignatureAsync(provider, bytes32Zeros, ethSignSignature, address)).to.be.false();
|
||||||
});
|
});
|
||||||
it("should return false if the address doesn't pertain to the signature & data", async () => {
|
it("should return false if the address doesn't pertain to the signature & data", async () => {
|
||||||
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
|
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
"description": "0x TS utils",
|
"description": "0x TS utils",
|
||||||
"main": "lib/index.js",
|
"main": "lib/src/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/src/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch_without_deps": "tsc -w",
|
"watch_without_deps": "tsc -w",
|
||||||
"build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts",
|
"build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts",
|
||||||
"clean": "shx rm -rf lib scripts",
|
"clean": "shx rm -rf lib scripts",
|
||||||
|
"test": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit",
|
||||||
"lint": "tslint --project .",
|
"lint": "tslint --project .",
|
||||||
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
|
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
|
||||||
},
|
},
|
||||||
@ -27,12 +28,15 @@
|
|||||||
"@0xproject/monorepo-scripts": "^1.0.4",
|
"@0xproject/monorepo-scripts": "^1.0.4",
|
||||||
"@0xproject/tslint-config": "^1.0.4",
|
"@0xproject/tslint-config": "^1.0.4",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
|
"@types/mocha": "^2.2.42",
|
||||||
"copyfiles": "^1.2.0",
|
"copyfiles": "^1.2.0",
|
||||||
"make-promises-safe": "^1.1.0",
|
"make-promises-safe": "^1.1.0",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"shx": "^0.2.2",
|
"shx": "^0.2.2",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"typescript": "2.9.2"
|
"typescript": "2.9.2",
|
||||||
|
"chai": "^4.0.1",
|
||||||
|
"mocha": "^4.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0xproject/types": "^1.0.1-rc.3",
|
"@0xproject/types": "^1.0.1-rc.3",
|
||||||
|
@ -1,7 +1,160 @@
|
|||||||
import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types';
|
import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { BigNumber } from './configured_bignumber';
|
||||||
|
|
||||||
|
export type EthersParamName = null | string | EthersNestedParamName;
|
||||||
|
|
||||||
|
export interface EthersNestedParamName {
|
||||||
|
name: string | null;
|
||||||
|
names: EthersParamName[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note(albrow): This function is unexported in ethers.js. Copying it here for
|
||||||
|
// now.
|
||||||
|
// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30
|
||||||
|
function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; types: string[] } {
|
||||||
|
const names: EthersParamName[] = [];
|
||||||
|
const types: string[] = [];
|
||||||
|
|
||||||
|
params.forEach((param: DataItem) => {
|
||||||
|
if (param.components != null) {
|
||||||
|
let suffix = '';
|
||||||
|
const arrayBracket = param.type.indexOf('[');
|
||||||
|
if (arrayBracket >= 0) { suffix = param.type.substring(arrayBracket); }
|
||||||
|
|
||||||
|
const result = parseEthersParams(param.components);
|
||||||
|
names.push({ name: (param.name || null), names: result.names });
|
||||||
|
types.push('tuple(' + result.types.join(',') + ')' + suffix);
|
||||||
|
} else {
|
||||||
|
names.push(param.name || null);
|
||||||
|
types.push(param.type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
names,
|
||||||
|
types,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if x is equal to y and false otherwise. Performs some minimal
|
||||||
|
// type conversion and data massaging for x and y, depending on type. name and
|
||||||
|
// type should typically be derived from parseEthersParams.
|
||||||
|
function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): boolean {
|
||||||
|
if (_.isUndefined(x) && _.isUndefined(y)) {
|
||||||
|
return true;
|
||||||
|
} else if (_.isUndefined(x) && !_.isUndefined(y)) {
|
||||||
|
return false;
|
||||||
|
} else if (!_.isUndefined(x) && _.isUndefined(y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_.endsWith(type, '[]')) {
|
||||||
|
// For array types, we iterate through the elements and check each one
|
||||||
|
// individually. Strangely, name does not need to be changed in this
|
||||||
|
// case.
|
||||||
|
if (x.length !== y.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const newType = _.trimEnd(type, '[]');
|
||||||
|
for (let i = 0; i < x.length; i++) {
|
||||||
|
if (!isAbiDataEqual(name, newType, x[i], y[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_.startsWith(type, 'tuple(')) {
|
||||||
|
if (_.isString(name)) {
|
||||||
|
throw new Error('Internal error: type was tuple but names was a string');
|
||||||
|
} else if (_.isNull(name)) {
|
||||||
|
throw new Error('Internal error: type was tuple but names was a null');
|
||||||
|
}
|
||||||
|
// For tuples, we iterate through the underlying values and check each
|
||||||
|
// one individually.
|
||||||
|
const types = splitTupleTypes(type);
|
||||||
|
if (types.length !== name.names.length) {
|
||||||
|
throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
// For tuples, name is an object with a names property that is an
|
||||||
|
// array. As an example, for orders, name looks like:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// name: 'orders',
|
||||||
|
// names: [
|
||||||
|
// 'makerAddress',
|
||||||
|
// // ...
|
||||||
|
// 'takerAssetData'
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
const nestedName = _.isString(name.names[i]) ? name.names[i] as string : (name.names[i] as EthersNestedParamName).name as string;
|
||||||
|
if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (type === 'address' || type === 'bytes') {
|
||||||
|
// HACK(albrow): ethers.js sometimes changes the case of addresses/bytes
|
||||||
|
// when decoding/encoding. To account for that, we convert to lowercase
|
||||||
|
// before comparing.
|
||||||
|
return _.isEqual(_.toLower(x), _.toLower(y));
|
||||||
|
} else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) {
|
||||||
|
return new BigNumber(x).eq(new BigNumber(y));
|
||||||
|
}
|
||||||
|
return _.isEqual(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is
|
||||||
|
// any other type or list of types) into its component types. It works with
|
||||||
|
// nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield:
|
||||||
|
// `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as
|
||||||
|
// an argument (not an array).
|
||||||
|
function splitTupleTypes(type: string): string[] {
|
||||||
|
if (_.endsWith(type, '[]')) {
|
||||||
|
throw new Error('Internal error: array types are not supported');
|
||||||
|
} else if (!_.startsWith(type, 'tuple(')) {
|
||||||
|
throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type);
|
||||||
|
}
|
||||||
|
// Trim the outtermost tuple().
|
||||||
|
const trimmedType = type.substring('tuple('.length, type.length - 1);
|
||||||
|
const types: string[] = [];
|
||||||
|
let currToken = '';
|
||||||
|
let parenCount = 0;
|
||||||
|
// Tokenize the type string while keeping track of parentheses.
|
||||||
|
for (const char of trimmedType) {
|
||||||
|
switch (char) {
|
||||||
|
case '(':
|
||||||
|
parenCount += 1;
|
||||||
|
currToken += char;
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
parenCount -= 1;
|
||||||
|
currToken += char;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
if (parenCount === 0) {
|
||||||
|
types.push(currToken);
|
||||||
|
currToken = '';
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
currToken += char;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
currToken += char;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types.push(currToken);
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
export const abiUtils = {
|
export const abiUtils = {
|
||||||
|
parseEthersParams,
|
||||||
|
isAbiDataEqual,
|
||||||
|
splitTupleTypes,
|
||||||
parseFunctionParam(param: DataItem): string {
|
parseFunctionParam(param: DataItem): string {
|
||||||
if (param.type === 'tuple') {
|
if (param.type === 'tuple') {
|
||||||
// Parse out tuple types into {type_1, type_2, ..., type_N}
|
// Parse out tuple types into {type_1, type_2, ..., type_N}
|
||||||
|
19
packages/utils/test/abi_utils_test.ts
Normal file
19
packages/utils/test/abi_utils_test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { abiUtils } from '../src';
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
describe('abiUtils', () => {
|
||||||
|
describe('splitTupleTypes', () => {
|
||||||
|
it('handles basic types', () => {
|
||||||
|
const got = abiUtils.splitTupleTypes('tuple(bytes,uint256,address)');
|
||||||
|
expect(got).to.deep.equal(['bytes', 'uint256', 'address']);
|
||||||
|
});
|
||||||
|
it('handles nested tuple types', () => {
|
||||||
|
const got = abiUtils.splitTupleTypes('tuple(tuple(bytes,uint256),address)');
|
||||||
|
expect(got).to.deep.equal(['tuple(bytes,uint256)', 'address']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,5 +3,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "lib"
|
"outDir": "lib"
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*"]
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"test/**/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user