UNPKG

ox

Version:

Ethereum Standard Library

718 lines (642 loc) 15.4 kB
import { expect, test } from 'vp/test' import { functionModifiers } from './signatures.js' import { isSolidityKeyword, isSolidityType, isValidDataLocation, parseAbiParameter, parseConstructorSignature, parseErrorSignature, parseEventSignature, parseFallbackSignature, parseFunctionSignature, parseSignature, splitParameters, } from './utils.js' const baseFunctionExpected = { name: 'foo', type: 'function', inputs: [], outputs: [], stateMutability: 'nonpayable', } const baseEventExpected = { name: 'Foo', type: 'event', inputs: [], } test.each([ { signature: 'function foo()', expected: baseFunctionExpected, }, { signature: 'function foo(string)', expected: { ...baseFunctionExpected, inputs: [{ type: 'string' }], }, }, { signature: 'function foo(string) view', expected: { ...baseFunctionExpected, inputs: [{ type: 'string' }], stateMutability: 'view', }, }, { signature: 'function foo(string) public view', expected: { ...baseFunctionExpected, inputs: [{ type: 'string' }], stateMutability: 'view', }, }, { signature: 'function foo(address payable to) external', expected: { ...baseFunctionExpected, inputs: [{ type: 'address', name: 'to' }], stateMutability: 'nonpayable', }, }, { signature: 'function foo(string) public view returns (string)', expected: { ...baseFunctionExpected, inputs: [{ type: 'string' }], outputs: [{ type: 'string' }], stateMutability: 'view', }, }, { signature: 'event Foo()', expected: baseEventExpected, }, { signature: 'event Foo(string indexed)', expected: { ...baseEventExpected, inputs: [{ type: 'string', indexed: true }], }, }, { signature: 'event Foo(string indexed foo)', expected: { ...baseEventExpected, inputs: [{ type: 'string', indexed: true, name: 'foo' }], }, }, { signature: 'error Foo(string foo)', expected: { name: 'Foo', type: 'error', inputs: [{ type: 'string', name: 'foo' }], }, }, { signature: 'receive() external payable', expected: { type: 'receive', stateMutability: 'payable', }, }, { signature: 'fallback() external payable', expected: { type: 'fallback', stateMutability: 'payable', }, }, { signature: 'fallback() external', expected: { type: 'fallback', stateMutability: 'nonpayable', }, }, ])('parseSignature($signature)', ({ signature, expected }) => { expect(parseSignature(signature)).toEqual(expected) }) test('invalid signature', () => { expect(() => parseSignature('')).toThrowErrorMatchingInlineSnapshot( `[Abi.UnknownSignatureError: Unknown signature.]`, ) expect(() => parseSignature('method foo(string) (address)'), ).toThrowErrorMatchingInlineSnapshot( ` [Abi.UnknownSignatureError: Unknown signature. Details: method foo(string) (address)] `, ) expect(() => parseSignature('error Foo(string memory foo)'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidModifierError: Invalid ABI parameter. Modifier "memory" not allowed in "error" type. Details: string memory foo] `, ) expect(() => parseSignature('event Foo(string memory foo)'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidModifierError: Invalid ABI parameter. Modifier "memory" not allowed in "event" type. Details: string memory foo] `, ) expect(() => parseSignature('function 9abc()'), ).toThrowErrorMatchingInlineSnapshot( ` [Abi.UnknownSignatureError: Unknown signature. Details: function 9abc()] `, ) expect(() => parseFunctionSignature('function foo() invalid'), ).toThrowErrorMatchingInlineSnapshot( ` [Abi.InvalidSignatureError: Invalid function signature. Details: function foo() invalid] `, ) expect(() => parseEventSignature('event Foo(string memory foo) invalid'), ).toThrowErrorMatchingInlineSnapshot( ` [Abi.InvalidSignatureError: Invalid event signature. Details: event Foo(string memory foo) invalid] `, ) expect(() => { parseErrorSignature('error Foo(string memory foo) invalid') }).toThrowErrorMatchingInlineSnapshot( ` [Abi.InvalidSignatureError: Invalid error signature. Details: error Foo(string memory foo) invalid] `, ) expect(() => { parseConstructorSignature('constructor() invalid') }).toThrowErrorMatchingInlineSnapshot( ` [Abi.InvalidSignatureError: Invalid constructor signature. Details: constructor() invalid] `, ) expect(() => { parseFallbackSignature('fallback() external invalid') }).toThrowErrorMatchingInlineSnapshot( ` [Abi.InvalidSignatureError: Invalid fallback signature. Details: fallback() external invalid] `, ) }) test('empty string', () => { expect(() => parseAbiParameter('')).toThrowErrorMatchingInlineSnapshot( `[HumanReadableAbi.InvalidParameterError: Invalid ABI parameter.]`, ) expect(() => parseAbiParameter('foo ,')).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParameterError: Invalid ABI parameter. Details: foo ,] `, ) }) test('Invalid solidity type', () => { expect(() => parseAbiParameter('strings')) .toThrowErrorMatchingInlineSnapshot(` [HumanReadableAbi.UnknownSolidityTypeError: Unknown type. Type "strings" is not a valid ABI type.] `) }) test('Invalid solidity type in tuple', () => { expect(() => parseAbiParameter('(strings)')) .toThrowErrorMatchingInlineSnapshot(` [HumanReadableAbi.UnknownSolidityTypeError: Unknown type. Type "strings" is not a valid ABI type.] `) }) test('Invalid solidity type in nested tuple', () => { expect(() => parseAbiParameter('((strings))')) .toThrowErrorMatchingInlineSnapshot(` [HumanReadableAbi.UnknownSolidityTypeError: Unknown type. Type "strings" is not a valid ABI type.] `) }) test('Struct type without context', () => { expect(() => parseAbiParameter('Demo demo')) .toThrowErrorMatchingInlineSnapshot(` [HumanReadableAbi.UnknownSolidityTypeError: Unknown type. Type "Demo" is not a valid ABI type.] `) }) test('Struct type with context', () => { expect(parseAbiParameter('Demo demo', { type: 'struct' })).toEqual({ type: 'Demo', name: 'demo', }) }) test('indexed not allowed', () => { expect(() => parseAbiParameter('string indexed foo'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidModifierError: Invalid ABI parameter. Modifier "indexed" not allowed. Details: string indexed foo] `, ) }) test('modifier not allowed', () => { expect(() => parseAbiParameter('uint256 calldata foo'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidModifierError: Invalid ABI parameter. Modifier "calldata" not allowed. Details: uint256 calldata foo] `, ) }) test('valid name', () => { expect(parseAbiParameter('uint256 $')).toEqual({ type: 'uint256', name: '$', }) expect(parseAbiParameter('uint256 $_a9')).toEqual({ type: 'uint256', name: '$_a9', }) expect(parseAbiParameter('uint256 _')).toEqual({ type: 'uint256', name: '_', }) expect(parseAbiParameter('uint256 abc$_9')).toEqual({ type: 'uint256', name: 'abc$_9', }) }) test('invalid name', () => { expect(() => parseAbiParameter('uint256 address'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.SolidityProtectedKeywordError: Invalid ABI parameter. "address" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html Details: uint256 address] `, ) expect(() => parseAbiParameter('uint256 9abc'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParameterError: Invalid ABI parameter. Details: uint256 9abc] `, ) }) test('invalid data location', () => { expect(() => parseAbiParameter('uint256 memory foo', { modifiers: functionModifiers }), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidFunctionModifierError: Invalid ABI parameter. Modifier "memory" not allowed. Data location can only be specified for array, struct, or mapping types, but "memory" was given. Details: uint256 memory foo] `, ) }) test('valid data location', () => { expect( parseAbiParameter('uint256[] memory foo', { modifiers: functionModifiers }), ).toMatchInlineSnapshot(` { "name": "foo", "type": "uint256[]", } `) expect( parseAbiParameter('string memory foo', { modifiers: functionModifiers }), ).toMatchInlineSnapshot(` { "name": "foo", "type": "string", } `) expect( parseAbiParameter('Foo memory foo', { modifiers: functionModifiers, structs: { Foo: [{ type: 'string' }] }, }), ).toMatchInlineSnapshot(` { "components": [ { "type": "string", }, ], "name": "foo", "type": "tuple", } `) }) test.each(['address', 'bool', 'bytes32', 'int256', 'string', 'uint256'])( 'parseAbiParameter($type)', (type) => { expect(parseAbiParameter(type)).toEqual({ type }) }, ) test.each([ 'address indexed', 'bool indexed', 'bytes32 indexed', 'int256 indexed', 'string indexed', 'uint256 indexed', ])("parseAbiParameter($type, { modifiers: 'indexed' })", (type) => { expect( parseAbiParameter(type, { modifiers: new Set(['indexed']), }), ).toEqual({ type: type.replace(' indexed', ''), indexed: true, }) }) test.each([ 'address[] calldata', 'bool[] calldata', 'bytes32[] calldata', 'int256[] calldata', 'string calldata', 'uint256[] calldata', 'bytes calldata', ])( "parseAbiParameter($type, { modifiers: ['calldata', 'memory'] })", (type) => { expect( parseAbiParameter(type, { modifiers: new Set(['calldata', 'memory']), }), ).toEqual({ type: type.replace(/\scalldata|memory/, ''), }) }, ) test.each([ 'address foo', 'bool foo', 'bytes32 foo', 'int256 foo', 'string foo', 'uint256 foo', ])('parseAbiParameter($type)', (type) => { expect(parseAbiParameter(type)).toEqual({ name: 'foo', type: type.replace(' foo', ''), }) }) test('dynamic integer', () => { expect(parseAbiParameter('int')).toMatchInlineSnapshot(` { "type": "int256", } `) expect(parseAbiParameter('uint')).toMatchInlineSnapshot(` { "type": "uint256", } `) }) test('structs', () => { expect( parseAbiParameter('Foo foo', { structs: { Foo: [{ type: 'string' }] } }), ).toMatchInlineSnapshot(` { "components": [ { "type": "string", }, ], "name": "foo", "type": "tuple", } `) expect( parseAbiParameter('Foo[] foo', { structs: { Foo: [{ type: 'string' }] } }), ).toMatchInlineSnapshot(` { "components": [ { "type": "string", }, ], "name": "foo", "type": "tuple[]", } `) }) test('inline tuples', () => { expect(parseAbiParameter('(string) foo')).toMatchInlineSnapshot(` { "components": [ { "type": "string", }, ], "name": "foo", "type": "tuple", } `) expect(parseAbiParameter('(string, string) foo')).toMatchInlineSnapshot(` { "components": [ { "type": "string", }, { "type": "string", }, ], "name": "foo", "type": "tuple", } `) expect( parseAbiParameter('(Foo, address bar) foo', { structs: { Foo: [{ type: 'string' }] }, }), ).toMatchInlineSnapshot(` { "components": [ { "components": [ { "type": "string", }, ], "type": "tuple", }, { "name": "bar", "type": "address", }, ], "name": "foo", "type": "tuple", } `) }) test.each([ { params: '', expected: [] }, { params: 'string', expected: ['string'] }, { params: 'string indexed foo', expected: ['string indexed foo'] }, { params: 'string, address', expected: ['string', 'address'] }, { params: 'string foo, address bar', expected: ['string foo', 'address bar'], }, { params: 'string indexed foo, address indexed bar', expected: ['string indexed foo', 'address indexed bar'], }, { params: 'address owner, (bool loading, (string[][] names) cats)[] dog, uint tokenId', expected: [ 'address owner', '(bool loading, (string[][] names) cats)[] dog', 'uint tokenId', ], }, { params: ' ', expected: [], }, ])('splitParameters($params)', ({ params, expected }) => { expect(splitParameters(params)).toEqual(expected) }) test.each([ 'address', 'bool', 'bytes32', 'int256', 'string', 'uint256', 'function', ])('isSolidityType($type)', (type) => { expect(isSolidityType(type)).toEqual(true) }) test('isSolidityType', () => { expect(isSolidityType('foo')).toEqual(false) }) test.each([ 'address', 'bool', 'bytes32', 'int256', 'string', 'uint256', 'function', 'view', 'override', 'let', 'var', 'typeof', 'promise', 'in', 'of', 'reference', 'implements', 'mapping', 'error', 'event', 'struct', 'alias', 'byte', 'case', 'copyof', 'final', 'external', 'public', 'internal', 'pure', 'match', 'apply', 'case', 'null', 'mutable', 'inline', 'static', 'partial', 'relocatable', 'try', 'catch', 'switch', 'supports', 'mapping', 'virtual', 'return', 'returns', 'after', 'auto', 'default', 'defined', 'typedef', 'typeof', ])('isInvalidSolidiyName($name)', (name) => { expect(isSolidityKeyword(name)).toEqual(true) }) test.each(['bytes', 'string', 'tuple'])( 'isValidDataLocation($type)', (type) => { expect(isValidDataLocation(type as any, false)).toEqual(true) }, ) test('Unbalanced Parethesis', () => { expect(() => splitParameters('address owner, ((string name)'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParenthesisError: Unbalanced parentheses. "((string name)" has too many opening parentheses. Details: Depth "1"] `, ) expect(() => splitParameters('address owner, (((string name)'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParenthesisError: Unbalanced parentheses. "(((string name)" has too many opening parentheses. Details: Depth "2"] `, ) expect(() => splitParameters('address owner, (string name))'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParenthesisError: Unbalanced parentheses. "(string name))" has too many closing parentheses. Details: Depth "-1"] `, ) expect(() => splitParameters('address owner, (string name)))'), ).toThrowErrorMatchingInlineSnapshot( ` [HumanReadableAbi.InvalidParenthesisError: Unbalanced parentheses. "(string name)))" has too many closing parentheses. Details: Depth "-2"] `, ) })