Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

document ethereum.js package #268

Merged
merged 6 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion yarn-project/ethereum.js/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('@aztec/foundation/eslint-legacy');
module.exports = require('@aztec/foundation/eslint');
555 changes: 513 additions & 42 deletions yarn-project/ethereum.js/src/contract/abi/abi-coder/ethers/abi-coder.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export const UNEXPECTED_ARGUMENT = 'UNEXPECTED_ARGUMENT';

const _censorErrors = false;

/**
* Throws a detailed error with a custom message, code, and additional information.
* The error message can be censored by setting the '_censorErrors' variable to true.
* In that case, a generic 'unknown error' message will be thrown instead of the custom message.
*
* @param message - The custom error message to display.
* @param code - The specific error code for this error (default is UNKNOWN_ERROR).
* @param params - An object containing additional information related to the error (e.g. argument name, value, etc.).
* @returns never - This function always throws an error and does not return any value.
*/
export function throwError(message: string, code: string = UNKNOWN_ERROR, params: any = {}): never {
if (_censorErrors) {
throw new Error('unknown error');
Expand Down Expand Up @@ -50,6 +60,16 @@ export function throwError(message: string, code: string = UNKNOWN_ERROR, params
throw error;
}

/**
* Validates the number of arguments provided against the expected count and throws an error if they do not match.
* This function is useful for checking the right number of arguments are passed to a function, especially in cases
* where optional arguments are involved. It appends a custom message suffix when provided.
*
* @param count - The actual number of arguments received by the function.
* @param expectedCount - The expected number of arguments for the function.
* @param suffix - Optional string to be appended to the error message when thrown.
* @throws {Error} If either too few or too many arguments are provided.
*/
export function checkArgumentCount(count: number, expectedCount: number, suffix?: string): void {
if (!suffix) {
suffix = '';
Expand Down
61 changes: 53 additions & 8 deletions yarn-project/ethereum.js/src/contract/abi/contract_abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@ import { LogResponse } from '../../eth_rpc/types/log_response.js';
import { bufferToHex } from '../../hex_string/index.js';
import { ContractAbiDefinition, ContractErrorEntry, ContractEventEntry, ContractFunctionEntry } from './index.js';

/**
* The ContractAbi class represents the ABI (Application Binary Interface) of a smart contract.
* It provides methods for decoding logs, events, and function data according to the contract's ABI definition.
* With a ContractAbi instance, you can match, decode and process logs and events generated by the smart contract,
* as well as decode input data provided when calling one of its functions.
*/
export class ContractAbi {
public functions: ContractFunctionEntry[];
public events: ContractEventEntry[];
public errors: ContractErrorEntry[];
public ctor: ContractFunctionEntry;
public fallback?: ContractFunctionEntry;
/**
* A list of contract functions.
*/
public functions: ContractFunctionEntry[];
/**
* An array containing contract event entries.
*/
public events: ContractEventEntry[];
/**
* A collection of error entries in the contract ABI.
*/
public errors: ContractErrorEntry[];
/**
* The constructor entry for the contract.
*/
public ctor: ContractFunctionEntry;
/**
* The fallback function to be executed when no other function matches the provided signature.
*/
public fallback?: ContractFunctionEntry;

constructor(definition: ContractAbiDefinition) {
this.functions = definition.filter(e => e.type === 'function').map(entry => new ContractFunctionEntry(entry));
Expand All @@ -21,19 +42,43 @@ export class ContractAbi {
}
}

public findEntryForLog(log: LogResponse) {
/**
* Find the matching event entry for a given log response in the contract ABI.
* This function iterates through the events defined in the ABI and compares their signatures with the log's topic.
* Returns the first matching event entry, or undefined if no match is found.
*
* @param log - The LogResponse object containing the log data to be matched against event signatures.
* @returns A ContractEventEntry instance that matches the log's topic, or undefined if no match is found.
*/
public findEntryForLog(log: LogResponse) {
return this.events.find(abiDef => abiDef.signature === log.topics[0]);
}

public decodeEvent(log: LogResponse) {
/**
* Decodes the event log data using the Contract ABI event definitions.
* Finds the matching event signature in the ABI, then decodes the log data accordingly.
* Throws an error if no matching event signature is found for the given log.
*
* @param log - The LogResponse object containing the event log data to be decoded.
* @returns A decoded event object with event name and decoded parameters.
*/
public decodeEvent(log: LogResponse) {
const event = this.findEntryForLog(log);
if (!event) {
throw new Error(`Unable to find matching event signature for log: ${log.id}`);
}
return event.decodeEvent(log);
}

public decodeFunctionData(data: Buffer) {
/**
* Decodes the function data from a given buffer and returns the decoded parameters.
* The input 'data' should contain the first 4 bytes as the function signature, followed by the encoded parameters.
* Returns undefined if no matching function is found in the ABI for the provided signature.
*
* @param data - The buffer containing the function signature and encoded parameters.
* @returns An object with the decoded parameters or undefined if no matching function is found.
*/
public decodeFunctionData(data: Buffer) {
const funcSig = bufferToHex(data.subarray(0, 4));
const func = this.functions.find(f => f.signature === funcSig);
return func ? func.decodeParameters(data.slice(4)) : undefined;
Expand Down
116 changes: 97 additions & 19 deletions yarn-project/ethereum.js/src/contract/abi/contract_abi_definition.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,109 @@
/**
* Represents the supported data types in Ethereum ABI (Application Binary Interface) for encoding and decoding contract interactions.
*/
export type AbiDataTypes = 'bool' | 'string' | 'address' | 'function' | 'uint' | 'int' | 'bytes' | string;

/**
* Type representing an individual input parameter in the ABI (Application Binary Interface) of a smart contract.
* It includes properties for the input's name, data type, and other relevant information used in encoding/decoding
* contract function calls and events.
*/
export type AbiInput = {
components?: any;
name: string;
type: AbiDataTypes;
indexed?: boolean;
internalType?: string;
/**
* Represents the structure of nested tuple elements.
*/
components?: any;
/**
* The name identifier for the contract entry.
*/
name: string;
/**
* Represents the type of a Contract Entry in the ABI (Application Binary Interface) definition.
*/
type: AbiDataTypes;
/**
* Indicates if the parameter is indexed in events.
*/
indexed?: boolean;
/**
* The internal representation of the data type.
*/
internalType?: string;
};

/**
* Represents the type definition for a single output parameter in a contract's ABI.
*/
export type AbiOutput = {
components?: any;
name: string;
type: AbiDataTypes;
internalType?: string;
/**
* Nested structure defining the data type components.
*/
components?: any;
/**
* The name identifier of the contract entry.
*/
name: string;
/**
* The type of contract entry, such as function, constructor, event, fallback, error, or receive.
*/
type: AbiDataTypes;
/**
* Represents the internal Solidity type of the input/output.
*/
internalType?: string;
};

/**
* Represents a single entry in a smart contract's ABI definition.
* Provides essential information about the contract's functions, events, constructors, and other elements,
* allowing effective interaction with the Ethereum blockchain.
*/
export interface ContractEntryDefinition {
constant?: boolean;
payable?: boolean;
anonymous?: boolean;
inputs?: AbiInput[];
name?: string;
outputs?: AbiOutput[];
type: 'function' | 'constructor' | 'event' | 'fallback' | 'error' | 'receive';
stateMutability?: 'pure' | 'view' | 'payable' | 'nonpayable';
signature?: string;
gas?: number;
/**
* Indicates if the contract entry is constant (read-only).
*/
constant?: boolean;
/**
* Indicates whether the contract entry can receive Ether.
*/
payable?: boolean;
/**
* Indicates if the event is anonymous, omitting event signature from logs.
*/
anonymous?: boolean;
/**
* An array of input parameters for the contract function or event.
*/
inputs?: AbiInput[];
/**
* The identifier for the contract function, event, or variable.
*/
name?: string;
/**
* An array of output parameters for the contract function or event.
*/
outputs?: AbiOutput[];
/**
* The type of contract entry, representing its purpose and functionality.
*/
type: 'function' | 'constructor' | 'event' | 'fallback' | 'error' | 'receive';
/**
* Represents the mutability of a contract's state during function execution.
*/
stateMutability?: 'pure' | 'view' | 'payable' | 'nonpayable';
/**
* The unique function identifier generated from the function's name and input types.
*/
signature?: string;
/**
* The estimated gas cost for executing the function.
*/
gas?: number;
}

/**
* Type representing the Application Binary Interface (ABI) definition for a smart contract,
* which consists of an array of ContractEntryDefinition objects. The ABI defines the
* structure of functions, events, and data types of a contract that can be interacted with.
*/
export type ContractAbiDefinition = ContractEntryDefinition[];
15 changes: 14 additions & 1 deletion yarn-project/ethereum.js/src/contract/abi/contract_entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { abiCoder } from './abi-coder/index.js';
import { ContractEntryDefinition } from './contract_abi_definition.js';

/**
* The ContractEntry class represents a single entry within an Ethereum smart contract's ABI definition.
* It provides easy access to the name of the function or event, as well as its anonymous status.
* Additionally, it offers a method to convert the entry into a human-readable string format.
* This class is primarily used for parsing and interacting with contract ABI definitions.
*/
export class ContractEntry {
constructor(protected entry: ContractEntryDefinition) {}

Expand All @@ -12,7 +18,14 @@ export class ContractEntry {
return this.entry.anonymous || false;
}

public asString() {
/**
* Returns a string representation of the ContractEntry instance using ABI encoding.
* This method utilizes the 'abiCoder' module to convert the contract entry definition
* into a readable and formatted string.
*
* @returns A string representation of the ContractEntry instance with ABI encoding.
*/
public asString() {
return abiCoder.abiMethodToString(this.entry);
}
}
62 changes: 56 additions & 6 deletions yarn-project/ethereum.js/src/contract/abi/contract_error_entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,45 @@ import { abiCoder } from './abi-coder/index.js';
import { ContractEntryDefinition } from './contract_abi_definition.js';
import { ContractEntry } from './contract_entry.js';

/**
* The ContractErrorEntry class extends the functionalities of the ContractEntry class for error handling in smart contracts.
* It handles encoding, decoding and managing error entries in a contract's ABI (Application Binary Interface).
* This class provides methods to encode and decode parameters, return values, and ABI for contract errors, ensuring proper communication with the blockchain.
*/
export class ContractErrorEntry extends ContractEntry {
public readonly signature: Buffer;
/**
* The encoded function signature for the contract entry.
*/
public readonly signature: Buffer;

constructor(entry: ContractEntryDefinition) {
entry.inputs = entry.inputs || [];
super(entry);
this.signature = hexToBuffer(abiCoder.encodeFunctionSignature(abiCoder.abiMethodToString(entry)));
}

public numArgs() {
/**
* Retrieve the number of input arguments for this contract error entry.
* This function returns the length of the 'inputs' array, which represents
* the input arguments required by the entry. If no inputs are defined,
* it returns 0.
*
* @returns The number of input arguments for the contract error entry.
*/
public numArgs() {
return this.entry.inputs ? this.entry.inputs.length : 0;
}

public decodeReturnValue(returnValue: Buffer) {
/**
* Decodes the return value of a contract function call using the ABI output definition.
* If there is only one output, returns the decoded output value directly; otherwise,
* returns an object containing the decoded values with the output names as keys.
* If the input returnValue buffer is empty, returns null.
*
* @param returnValue - The Buffer containing the encoded return value of the contract function call.
* @returns Decoded output value(s) or null if returnValue is empty.
*/
public decodeReturnValue(returnValue: Buffer) {
if (!returnValue.length) {
return null;
}
Expand All @@ -31,15 +56,40 @@ export class ContractErrorEntry extends ContractEntry {
}
}

public encodeABI(args: any[]) {
/**
* Encodes the ABI (Application Binary Interface) of a function call by concatenating the function's signature
* and encoded input parameters. This resulting buffer can be used for encoding the data field of a transaction.
* The 'args' array should contain values that match the expected input types of the function.
*
* @param args - An array of arguments matching the function's input parameters.
* @returns A Buffer containing the encoded ABI for the function call.
*/
public encodeABI(args: any[]) {
return Buffer.concat([this.signature, this.encodeParameters(args)]);
}

public encodeParameters(args: any[]) {
/**
* Encode the input parameters according to the contract entry inputs.
* This function takes an array of arguments and encodes them into a Buffer
* following the Solidity contract's entry ABI specifications.
*
* @param args - An array of input values matching the contract entry inputs.
* @returns A Buffer containing the encoded parameters.
*/
public encodeParameters(args: any[]) {
return abiCoder.encodeParameters(this.entry.inputs, args);
}

public decodeParameters(bytes: Buffer) {
/**
* Decode the provided bytes buffer into parameters based on the entry inputs.
* This function helps in interpreting the raw bytes buffer received from a contract call
* or an event log, by decoding it based on the ABI input types, and returning the
* decoded values as an object with the input names as keys.
*
* @param bytes - The Buffer containing the encoded parameters to be decoded.
* @returns An object with decoded parameters, keys mapped to the input names defined in the ABI.
*/
public decodeParameters(bytes: Buffer) {
return abiCoder.decodeParameters(this.entry.inputs, bytes);
}
}
Loading