ox
Version:
Ethereum Standard Library
718 lines (642 loc) • 15.4 kB
text/typescript
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"]
`,
)
})