Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
This commit is contained in:
		@@ -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/**/*"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user