Skip to content

Commit

Permalink
feat: inferring types for events and functions using the ABI (#1444)
Browse files Browse the repository at this point in the history
* feat: added types to decoded events

* feat: more changes, still need to fix decodeFunctionOutput

* feat: fixed test

* feat: added package to check types

* feat: added package to check types

* feat: added package to check types

* feat: added package to check types

* feat: added package to check types

* feat: added package to check types

* feat: added package to check types

* feat: relying on the type

* feat: added more type checks

* feat: using default values for types

* feat: using default values for types

* feat: using default values for types

* feat: allowing standard string so we use variables

* feat: added another type check

* feat: added another type check

* feat: added another type check

---------

Co-authored-by: Fabio Rigamonti <[email protected]>
  • Loading branch information
freemanzMrojo and fabiorigam authored Oct 30, 2024
1 parent 2993e32 commit a67af5a
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 123 deletions.
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"devDependencies": {
"bignumber.js": "^9.1.2",
"thor-devkit": "^2.0.9"
"thor-devkit": "^2.0.9",
"tsd": "^0.31.2"
}
}
4 changes: 2 additions & 2 deletions packages/core/src/transaction/Clause.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidDataType } from '@vechain/sdk-errors';
import { ERC721_ABI, VIP180_ABI } from '../utils';
import {
ABI,
Expand All @@ -11,10 +12,9 @@ import {
} from '../vcdm';
import { Hex } from '../vcdm/Hex';
import { HexInt } from '../vcdm/HexInt';
import { InvalidDataType } from '@vechain/sdk-errors';
import type { ClauseOptions } from './ClauseOptions';
import type { DeployParams } from './DeployParams';
import type { TransactionClause } from './TransactionClause';
import type { ClauseOptions } from './ClauseOptions';

/**
* This class represent a transaction clause.
Expand Down
121 changes: 75 additions & 46 deletions packages/core/src/vcdm/abi/ABIContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
getAbiItem,
type AbiEvent,
type AbiFunction,
type ContractEventName,
type ContractFunctionName,
type DecodeEventLogReturnType,
type DecodeFunctionDataReturnType,
type DecodeFunctionResultReturnType,
Expand All @@ -16,20 +18,20 @@ import { ABI } from './ABI';
import { ABIEvent, type ABIEventData } from './ABIEvent';
import { ABIFunction } from './ABIFunction';

class ABIContract extends ABI {
private readonly abi: ViemABI;
class ABIContract<TAbi extends ViemABI> extends ABI {
private readonly viemABI: ViemABI;

constructor(abi: ViemABI) {
constructor(readonly abi: TAbi) {
super();
this.abi = abi;
this.viemABI = abi as ViemABI;
}

/**
* Creates an ABIContract instance from a viem ABI.
* @param {ViemABI} abi representation of the contract.
* @returns New instance of ABIContract.
*/
public static ofAbi(abi: ViemABI): ABIContract {
public static ofAbi<TAbi extends ViemABI>(abi: TAbi): ABIContract<TAbi> {
return new ABIContract(abi);
}

Expand All @@ -39,10 +41,12 @@ class ABIContract extends ABI {
* @returns {ABIFunction} The function with the given name.
* @throws {InvalidAbiItem}
*/
public getFunction(name: string): ABIFunction {
public getFunction<TFunctionName extends ContractFunctionName<TAbi>>(
name: TFunctionName | string
): ABIFunction<TAbi, TFunctionName> {
const functionAbiItem = getAbiItem({
abi: this.abi,
name
abi: this.viemABI,
name: name as string
});
if (functionAbiItem === null || functionAbiItem === undefined) {
throw new InvalidAbiItem(
Expand All @@ -54,7 +58,9 @@ class ABIContract extends ABI {
}
);
}
return new ABIFunction(functionAbiItem as AbiFunction);
return new ABIFunction<TAbi, TFunctionName>(
functionAbiItem as AbiFunction
);
}

/**
Expand All @@ -63,10 +69,12 @@ class ABIContract extends ABI {
* @returns {ABIEvent} The event with the given name.
* @throws {InvalidAbiItem}
*/
public getEvent(name: string): ABIEvent {
public getEvent<TEventName extends ContractEventName<TAbi>>(
name: TEventName | string
): ABIEvent<TAbi, TEventName> {
const eventAbiItem = getAbiItem({
abi: this.abi,
name
abi: this.viemABI,
name: name as string
});
if (eventAbiItem === null || eventAbiItem === undefined) {
throw new InvalidAbiItem(
Expand All @@ -78,7 +86,7 @@ class ABIContract extends ABI {
}
);
}
return new ABIEvent(eventAbiItem as AbiEvent);
return new ABIEvent<TAbi, TEventName>(eventAbiItem as AbiEvent);
}

/**
Expand All @@ -88,16 +96,17 @@ class ABIContract extends ABI {
* @returns {Hex} The encoded data in hexadecimal that can be used to send a transaction.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public encodeFunctionInput(
functionName: string,
functionData?: unknown[]
): Hex {
public encodeFunctionInput<
TFunctionName extends ContractFunctionName<TAbi>
>(functionName: TFunctionName | string, functionData?: unknown[]): Hex {
try {
const functionAbiItem = getAbiItem({
abi: this.abi,
name: functionName
abi: this.viemABI,
name: functionName as string
});
const functionAbi = new ABIFunction(functionAbiItem as AbiFunction);
const functionAbi = new ABIFunction<TAbi, TFunctionName>(
functionAbiItem as AbiFunction
);

return functionAbi.encodeData(functionData);
} catch (error) {
Expand All @@ -117,16 +126,20 @@ class ABIContract extends ABI {
* @returns {DecodeFunctionDataReturnType} an array of the decoded function data
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public decodeFunctionInput(
functionName: string,
public decodeFunctionInput<
TFunctionName extends ContractFunctionName<TAbi>
>(
functionName: TFunctionName | string,
encodedFunctionInput: Hex
): DecodeFunctionDataReturnType {
): DecodeFunctionDataReturnType<TAbi, TFunctionName> {
try {
const functionAbiItem = getAbiItem({
abi: this.abi,
name: functionName
abi: this.viemABI,
name: functionName as string
});
const functionAbi = new ABIFunction(functionAbiItem as AbiFunction);
const functionAbi = new ABIFunction<TAbi, TFunctionName>(
functionAbiItem as AbiFunction
);

return functionAbi.decodeData(encodedFunctionInput);
} catch (error) {
Expand Down Expand Up @@ -154,16 +167,20 @@ class ABIContract extends ABI {
* const decodedOutput = decodeFunctionOutput('getValue', encodedValue);
*
*/
public decodeFunctionOutput(
functionName: string,
public decodeFunctionOutput<
TFunctionName extends ContractFunctionName<TAbi>
>(
functionName: TFunctionName | string,
encodedFunctionOutput: Hex
): DecodeFunctionResultReturnType {
): DecodeFunctionResultReturnType<TAbi, TFunctionName> {
try {
const functionAbiItem = getAbiItem({
abi: this.abi,
name: functionName
abi: this.viemABI,
name: functionName as string
});
const functionAbi = new ABIFunction(functionAbiItem as AbiFunction);
const functionAbi = new ABIFunction<TAbi, TFunctionName>(
functionAbiItem as AbiFunction
);

return functionAbi.decodeResult(encodedFunctionOutput);
} catch (error) {
Expand All @@ -183,16 +200,18 @@ class ABIContract extends ABI {
* @returns {ABIEventData} An object containing the encoded data and topics.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public encodeEventLog(
eventName: string,
public encodeEventLog<TEventName extends ContractEventName<TAbi>>(
eventName: TEventName | string,
eventArgs: unknown[]
): ABIEventData {
try {
const eventAbiItem = getAbiItem({
abi: this.abi,
name: eventName
abi: this.viemABI,
name: eventName as string
});
const eventAbi = new ABIEvent(eventAbiItem as AbiEvent);
const eventAbi = new ABIEvent<TAbi, TEventName>(
eventAbiItem as AbiEvent
);
return eventAbi.encodeEventLog(eventArgs);
} catch (error) {
throw new InvalidAbiDataToEncodeOrDecode(
Expand All @@ -211,16 +230,18 @@ class ABIContract extends ABI {
* @returns {DecodeEventLogReturnType} The decoded data of the event log.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public decodeEventLog(
eventName: string,
public decodeEventLog<TEventName extends ContractEventName<TAbi>>(
eventName: TEventName | string,
eventToDecode: ABIEventData
): DecodeEventLogReturnType {
): DecodeEventLogReturnType<TAbi, TEventName> {
try {
const eventAbiItem = getAbiItem({
abi: this.abi,
name: eventName
abi: this.viemABI,
name: eventName as string
});
const eventAbi = new ABIEvent(eventAbiItem as AbiEvent);
const eventAbi = new ABIEvent<TAbi, TEventName>(
eventAbiItem as AbiEvent
);
return eventAbi.decodeEventLog(eventToDecode);
} catch (error) {
throw new InvalidAbiDataToEncodeOrDecode(
Expand All @@ -245,9 +266,15 @@ class ABIContract extends ABI {
* @returns {DecodeEventLogReturnType} - A log object representing the decoded log or null if decoding fails.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public parseLog(data: Hex, topics: Hex[]): DecodeEventLogReturnType {
public parseLog<TEventName extends ContractEventName<TAbi>>(
data: Hex,
topics: Hex[]
): DecodeEventLogReturnType<TAbi, TEventName> {
try {
return ABIEvent.parseLog(this.abi, { data, topics });
return ABIEvent.parseLog(this.abi, {
data,
topics
});
} catch (e) {
throw new InvalidAbiDataToEncodeOrDecode(
'ABIContract.parseLog()',
Expand All @@ -272,7 +299,9 @@ class ABIContract extends ABI {
return [];
}

return this.parseObjectValues(eventLogDecoded.args);
return this.parseObjectValues(
eventLogDecoded.args as unknown as object
);
}
}

Expand Down
23 changes: 16 additions & 7 deletions packages/core/src/vcdm/abi/ABIEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { type AbiEventParameter } from 'abitype';
import {
type AbiEvent,
type ContractEventName,
type DecodeEventLogReturnType,
encodeEventTopics,
type EncodeEventTopicsReturnType,
Expand All @@ -27,7 +28,10 @@ interface ABIEventData {
* Represents a function call in the Event ABI.
* @extends ABIItem
*/
class ABIEvent extends ABIItem {
class ABIEvent<
TAbi extends ViemABI = ViemABI,
TEventName extends ContractEventName<TAbi> = ContractEventName<TAbi>
> extends ABIItem {
private readonly abiEvent: AbiEvent;
public constructor(signature: string);
public constructor(signature: AbiEvent);
Expand Down Expand Up @@ -55,10 +59,13 @@ class ABIEvent extends ABIItem {
* @returns Decoding results.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public static parseLog(
abi: ViemABI,
public static parseLog<
TAbi extends ViemABI,
TEventName extends ContractEventName<TAbi>
>(
abi: TAbi,
eventData: ABIEventData
): DecodeEventLogReturnType {
): DecodeEventLogReturnType<TAbi, TEventName> {
try {
return viemDecodeEventLog({
abi,
Expand Down Expand Up @@ -95,9 +102,11 @@ class ABIEvent extends ABIItem {
* @returns Decoding results.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public decodeEventLog(event: ABIEventData): DecodeEventLogReturnType {
public decodeEventLog(
event: ABIEventData
): DecodeEventLogReturnType<TAbi, TEventName> {
try {
return ABIEvent.parseLog([this.abiEvent], event);
return ABIEvent.parseLog([this.abiEvent] as ViemABI, event);
} catch (error) {
throw new InvalidAbiDataToEncodeOrDecode(
'ABIEvent.decodeEventLog',
Expand All @@ -120,7 +129,7 @@ class ABIEvent extends ABIItem {
return [];
}

return this.parseObjectValues(rawDecodedData.args);
return this.parseObjectValues(rawDecodedData.args as unknown as object);
}

/**
Expand Down
23 changes: 19 additions & 4 deletions packages/core/src/vcdm/abi/ABIFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
} from '@vechain/sdk-errors';
import {
type AbiFunction,
type ContractFunctionName,
decodeFunctionData,
type DecodeFunctionDataReturnType,
decodeFunctionResult,
type DecodeFunctionResultReturnType,
encodeFunctionData,
type Abi as ViemABI,
type Hex as ViemHex
} from 'viem';
import { Hex } from '../Hex';
Expand All @@ -18,7 +20,11 @@ import { ABIItem } from './ABIItem';
* Represents a function call in the Function ABI.
* @extends ABIItem
*/
class ABIFunction extends ABIItem {
class ABIFunction<
TAbi extends ViemABI = ViemABI,
TFunctionName extends
ContractFunctionName<TAbi> = ContractFunctionName<TAbi>
> extends ABIItem {
private readonly abiFunction: AbiFunction;
public constructor(signature: string);
public constructor(signature: AbiFunction);
Expand Down Expand Up @@ -55,7 +61,9 @@ class ABIFunction extends ABIItem {
* @returns Decoding results.
* @throws {InvalidAbiDataToEncodeOrDecode}
*/
public decodeData(data: Hex): DecodeFunctionDataReturnType {
public decodeData(
data: Hex
): DecodeFunctionDataReturnType<TAbi, TFunctionName> {
try {
return decodeFunctionData({
abi: [this.abiFunction],
Expand Down Expand Up @@ -110,12 +118,19 @@ class ABIFunction extends ABIItem {
* console.log('Decoded Output:', decoded);
* ```
*/
public decodeResult(data: Hex): DecodeFunctionResultReturnType {
public decodeResult(
data: Hex
): DecodeFunctionResultReturnType<TAbi, TFunctionName> {
try {
return decodeFunctionResult({
const result = decodeFunctionResult({
abi: [this.abiFunction],
data: data.toString() as ViemHex
});

return result as DecodeFunctionResultReturnType<
TAbi,
TFunctionName
>;
} catch (error) {
throw new InvalidAbiDataToEncodeOrDecode(
'ABIFunction.decodeResult',
Expand Down
Loading

1 comment on commit a67af5a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage

Summary

Lines Statements Branches Functions
Coverage: 99%
99.06% (4352/4393) 97.71% (1413/1446) 99.11% (896/904)
Title Tests Skipped Failures Errors Time
core 808 0 💤 0 ❌ 0 🔥 2m 17s ⏱️
network 734 0 💤 0 ❌ 0 🔥 4m 50s ⏱️
errors 42 0 💤 0 ❌ 0 🔥 16.693s ⏱️

Please sign in to comment.