Skip to content

Commit

Permalink
Add contract static type support
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarhussain committed Dec 6, 2021
1 parent c919c9b commit 8877b33
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 109 deletions.
4 changes: 2 additions & 2 deletions packages/web3-eth-abi/src/api/events_api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AbiError } from 'web3-common';
import { sha3Raw } from 'web3-utils';
import { JsonAbiEventFragment } from '../types';
import { AbiEventFragment } from '../types';
import { jsonInterfaceMethodToString, isAbiEventFragment } from '../utils';

/**
* Encodes the event name to its ABI signature, which are the sha3 hash of the event name including input types..
*/
export const encodeEventSignature = (functionName: string | JsonAbiEventFragment): string => {
export const encodeEventSignature = (functionName: string | AbiEventFragment): string => {
if (typeof functionName !== 'string' && !isAbiEventFragment(functionName)) {
throw new AbiError('Invalid parameter value in encodeEventSignature');
}
Expand Down
6 changes: 3 additions & 3 deletions packages/web3-eth-abi/src/api/functions_api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AbiError } from 'web3-common';
import { sha3Raw } from 'web3-utils';
import { isAbiFunctionFragment, jsonInterfaceMethodToString } from '../utils';
import { JsonAbiFunctionFragment } from '../types';
import { AbiFunctionFragment } from '../types';
import { encodeParameters } from './parameters_api';

/**
* Encodes the function name to its ABI representation, which are the first 4 bytes of the sha3 of the function name including types.
*/
export const encodeFunctionSignature = (functionName: string | JsonAbiFunctionFragment): string => {
export const encodeFunctionSignature = (functionName: string | AbiFunctionFragment): string => {
if (typeof functionName !== 'string' && !isAbiFunctionFragment(functionName)) {
throw new AbiError('Invalid parameter value in encodeFunctionSignature');
}
Expand All @@ -27,7 +27,7 @@ export const encodeFunctionSignature = (functionName: string | JsonAbiFunctionFr
* Encodes a function call from its json interface and parameters.
*/
export const encodeFunctionCall = (
jsonInterface: JsonAbiFunctionFragment,
jsonInterface: AbiFunctionFragment,
params: unknown[],
): string => {
if (!isAbiFunctionFragment(jsonInterface)) {
Expand Down
12 changes: 6 additions & 6 deletions packages/web3-eth-abi/src/api/logs_api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HexString } from 'web3-utils';
import { JsonAbiParameter } from '../types';
import { AbiParameter } from '../types';
import { decodeParameter, decodeParametersWith } from './parameters_api';

const STATIC_TYPES = ['bool', 'string', 'int', 'uint', 'address', 'fixed', 'ufixed'];
Expand All @@ -8,28 +8,28 @@ const STATIC_TYPES = ['bool', 'string', 'int', 'uint', 'address', 'fixed', 'ufix
* Decodes ABI-encoded log data and indexed topic data
*/
export const decodeLog = <ReturnType extends Record<string, unknown>>(
inputs: Array<JsonAbiParameter>,
inputs: Array<AbiParameter>,
data: HexString,
topics: string | string[],
) => {
const clonedTopics = Array.isArray(topics) ? topics : [topics];

const clonedData = data ?? '';

const notIndexedInputs: Array<string | JsonAbiParameter> = [];
const indexedParams: Array<string | JsonAbiParameter> = [];
const notIndexedInputs: Array<string | AbiParameter> = [];
const indexedParams: Array<string | AbiParameter> = [];
let topicCount = 0;

// TODO check for anonymous logs?
for (const [i, input] of inputs.entries()) {
if (input.indexed) {
indexedParams[i] = STATIC_TYPES.some(s => input.type.startsWith(s))
? (decodeParameter(input.type, clonedTopics[topicCount]) as JsonAbiParameter)
? (decodeParameter(input.type, clonedTopics[topicCount]) as AbiParameter)
: clonedTopics[topicCount];

topicCount += 1;
} else {
notIndexedInputs[i] = input as unknown as JsonAbiParameter;
notIndexedInputs[i] = input as unknown as AbiParameter;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/web3-eth-abi/src/api/parameters_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { formatParam, isAbiFragment, mapTypes, modifyParams } from '../utils';
/**
* Should be used to encode list of params
*/
export const encodeParameters = (abi: AbiInput[], params: unknown[]): string => {
export const encodeParameters = (abi: ReadonlyArray<AbiInput>, params: unknown[]): string => {
try {
const modifiedTypes = mapTypes(Array.isArray(abi) ? abi : [abi]);
const modifiedParams: Array<unknown> = [];
Expand Down
7 changes: 7 additions & 0 deletions packages/web3-eth-abi/src/number_map_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// TODO: Convert it to static file
type _SolidityIndexRange = 1 | 2 | 3 | 4 | 5;

export type ConvertToNumber<
T extends string,
Range extends number = _SolidityIndexRange,
> = Range extends unknown ? (`${Range}` extends T ? Range : never) : never;
206 changes: 168 additions & 38 deletions packages/web3-eth-abi/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,187 @@
// TODO: Adding reference of source definition/doc for these types
export interface JsonAbiParameter {
readonly name: string;
readonly type: string;
readonly baseType?: string;
readonly indexed?: boolean;
readonly components?: Array<JsonAbiParameter>;
readonly arrayLength?: number;
readonly arrayChildren?: Array<JsonAbiParameter>;
}
import {
Address,
Bytes,
Numbers,
ObjectValueToTuple,
ArrayToIndexObject,
FixedSizeArray,
} from 'web3-utils';
import { ConvertToNumber } from './number_map_type';

export interface JsonAbiStruct {
export interface AbiStruct {
[key: string]: unknown;
name?: string;
type: string;
}

export interface JsonAbiCoderStruct extends JsonAbiStruct {
export interface AbiCoderStruct extends AbiStruct {
[key: string]: unknown;
components?: Array<JsonAbiStruct>;
components?: Array<AbiStruct>;
}

// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type AbiParameter = {
readonly name: string;
readonly type: string;
readonly baseType?: string;
readonly indexed?: boolean;
readonly components?: ReadonlyArray<AbiParameter>;
readonly arrayLength?: number;
readonly arrayChildren?: ReadonlyArray<AbiParameter>;
};

type FragmentTypes = 'constructor' | 'event' | 'function';

export interface JsonAbiBaseFragment {
name?: string;
type: FragmentTypes;
inputs?: Array<JsonAbiParameter>;
}
export type AbiBaseFragment = {
readonly type: FragmentTypes;
};

export interface JsonAbiConstructorFragment extends JsonAbiBaseFragment {
type: 'constructor';
stateMutability: 'nonpayable' | 'payable';
}
// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type AbiConstructorFragment = AbiBaseFragment & {
readonly type: 'constructor';
readonly stateMutability: 'nonpayable' | 'payable';
readonly inputs: ReadonlyArray<AbiParameter>;
};

export interface JsonAbiFunctionFragment extends JsonAbiBaseFragment {
type: 'function';
stateMutability: 'nonpayable' | 'payable' | 'pure' | 'view';
outputs?: Array<JsonAbiParameter>;
// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type AbiFunctionFragment = AbiBaseFragment & {
readonly name: string;
readonly type: 'function';
readonly stateMutability: 'nonpayable' | 'payable' | 'pure' | 'view';
readonly inputs: ReadonlyArray<AbiParameter>;
readonly outputs: ReadonlyArray<AbiParameter>;

// legacy properties
constant?: boolean; // stateMutability == 'pure' or stateMutability == 'view'
payable?: boolean; // stateMutability == 'payable'
}
readonly constant?: boolean; // stateMutability == 'pure' or stateMutability == 'view'
readonly payable?: boolean; // stateMutability == 'payable'
};

export interface JsonAbiEventFragment extends JsonAbiBaseFragment {
type: 'event';
anonymous?: boolean;
}
// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type AbiEventFragment = AbiBaseFragment & {
readonly name: string;
readonly type: 'event';
readonly inputs: ReadonlyArray<AbiParameter>;
readonly anonymous?: boolean;
};

// https://docs.soliditylang.org/en/latest/abi-spec.html#json
export type JsonAbiFragment =
| JsonAbiConstructorFragment
| JsonAbiFunctionFragment
| JsonAbiEventFragment;
export type AbiFragment = AbiConstructorFragment | AbiFunctionFragment | AbiEventFragment;

export type ContractAbi = ReadonlyArray<AbiFragment>;

export type AbiInput = string | AbiParameter | { readonly [key: string]: unknown };

export type FilterAbis<Abis extends ContractAbi, Filter, Abi = Abis[number]> = Abi extends Filter
? Abi
: never;

type _TypedArray<Type, Size extends string> = Size extends ''
? Type[]
: FixedSizeArray<Type, ConvertToNumber<Size>>;

export type PrimitiveAddressType<Type extends string> = Type extends `address[${infer Size}]`
? _TypedArray<Address, Size>
: Type extends 'address'
? Address
: never;

export type PrimitiveStringType<Type extends string> = Type extends `string${string}[${infer Size}]`
? _TypedArray<string, Size>
: Type extends 'string' | `string${string}`
? string
: never;

export type PrimitiveBooleanType<Type extends string> = Type extends `bool[${infer Size}]`
? _TypedArray<boolean, Size>
: Type extends 'bool'
? boolean
: never;

export type PrimitiveIntegerType<Type extends string> = Type extends `uint${string}[${infer Size}]`
? _TypedArray<Numbers, Size>
: Type extends 'uint' | 'int' | `int${string}` | `uint${string}`
? Numbers
: never;

export type PrimitiveBytesType<Type extends string> = Type extends `bytes${string}[${infer Size}]`
? _TypedArray<Bytes, Size>
: Type extends 'bytes' | `bytes${string}`
? Bytes
: never;

export type PrimitiveTupleType<
Type extends string,
Components extends ReadonlyArray<AbiParameter> | undefined = [],
> = Components extends ReadonlyArray<AbiParameter>
? Type extends 'tuple'
? {
// eslint-disable-next-line no-use-before-define
[Param in Components[number] as Param['name']]: MatchPrimitiveType<
Param['type'],
Param['components']
>;
}
: Type extends `tuple[${infer Size}]`
? _TypedArray<
{
// eslint-disable-next-line no-use-before-define
[Param in Components[number] as Param['name']]: MatchPrimitiveType<
Param['type'],
Param['components']
>;
},
Size
>
: never
: never;

export type MatchPrimitiveType<
Type extends string,
Components extends ReadonlyArray<AbiParameter> | undefined,
> =
| PrimitiveAddressType<Type>
| PrimitiveStringType<Type>
| PrimitiveBooleanType<Type>
| PrimitiveIntegerType<Type>
| PrimitiveBytesType<Type>
| PrimitiveTupleType<Type, Components>
| Type;

// Only intended to use locally so why not exported
// TODO: Inspect Record<string, AbiParameter> not working constraint
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type _ExtractParameterType<T extends Record<string, any>> = {
[K in keyof T]: MatchPrimitiveType<T[K]['type'], T[K]['components']>;
};

export type ContractMethodOutputParameters<Params extends ReadonlyArray<AbiParameter>> =
ObjectValueToTuple<_ExtractParameterType<ArrayToIndexObject<Params>>>;

export type ContractMethodInputParameters<Params extends ReadonlyArray<AbiParameter>> = {
[Param in Params[number] as Param['name']]: MatchPrimitiveType<
Param['type'],
Param['components']
>;
};

export type ContractConstructor<Abis extends ContractAbi> = {
[Abi in FilterAbis<Abis, AbiConstructorFragment> as 'constructor']: {
readonly Abi: Abi;
readonly Inputs: ContractMethodInputParameters<Abi['inputs']>;
};
}['constructor'];

export type ContractMethods<Abis extends ContractAbi> = {
[Abi in FilterAbis<Abis, AbiFunctionFragment> as Abi['name']]: {
readonly Abi: Abi;
readonly Inputs: ContractMethodInputParameters<Abi['inputs']>;
readonly Outputs: ContractMethodOutputParameters<Abi['outputs']>;
};
};

export type AbiInput = string | JsonAbiParameter | { readonly [key: string]: unknown };
export type ContractEvents<Abis extends ContractAbi> = {
[Abi in FilterAbis<Abis, AbiEventFragment> as Abi['name']]: {
readonly Abi: Abi;
readonly Inputs: ContractMethodInputParameters<Abi['inputs']>;
};
};
Loading

0 comments on commit 8877b33

Please sign in to comment.