Skip to content

Commit

Permalink
Merge pull request #462 from NomicFoundation/refactor/tuple-indexing
Browse files Browse the repository at this point in the history
Add support for return value indexing in `readEventArg` and `staticCall`
  • Loading branch information
zoeyTM authored Sep 14, 2023
2 parents 2b550c3 + 74743c7 commit 768a9a8
Show file tree
Hide file tree
Showing 33 changed files with 445 additions and 72 deletions.
45 changes: 41 additions & 4 deletions packages/core/src/internal/execution/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export function getEventArgumentFromReceipt(
emitterAddress: string,
eventName: string,
eventIndex: number,
argument: string | number
nameOrIndex: string | number
): EvmValue {
const emitterLogs = receipt.logs.filter((l) => l.address === emitterAddress);

Expand All @@ -300,11 +300,11 @@ export function getEventArgumentFromReceipt(

const evmTuple = ethersResultIntoEvmTuple(ethersResult, eventFragment.inputs);

if (typeof argument === "string") {
return evmTuple.named[argument];
if (typeof nameOrIndex === "string") {
return evmTuple.named[nameOrIndex];
}

return evmTuple.positional[argument];
return evmTuple.positional[nameOrIndex];
}

/**
Expand Down Expand Up @@ -697,6 +697,43 @@ function getEventArgumentParamType(
return paramType;
}

/**
* Validates the param type of a static call return value, throwing a validation error if it's not found.
*/
export function validateFunctionArgumentParamType(
contractName: string,
functionName: string,
artifact: Artifact,
argument: string | number
): void {
const { ethers } = require("ethers") as typeof import("ethers");
const iface = new ethers.Interface(artifact.abi);
const functionFragment = getFunctionFragment(iface, functionName);

if (typeof argument === "string") {
let hasArg = false;
for (const output of functionFragment.outputs) {
if (output.name === argument) {
hasArg = true;
}
}

if (!hasArg) {
throw new IgnitionValidationError(
`Function ${functionName} of contract ${contractName} has no return value named ${argument}`
);
}
} else {
const paramType = functionFragment.outputs[argument];

if (paramType === undefined) {
throw new IgnitionValidationError(
`Function ${functionName} of contract ${contractName} has only ${functionFragment.outputs.length} return values, but value ${argument} was requested`
);
}
}
}

/**
* Returns true if the given param type has a dynamic size.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
StaticCallResponse,
SuccessfulTransaction,
} from "./types/execution-strategy";
import { convertEvmTupleToSolidityParam } from "./utils/convert-evm-tuple-to-solidity-param";

/**
* Returns true if the given response is an onchain interaction response.
Expand Down Expand Up @@ -205,12 +204,16 @@ export async function* executeStaticCallRequest(
* @returns The value that should be used as the result of the static call execution state.
*/
export function getStaticCallExecutionStateResultValue(
_exState: StaticCallExecutionState,
exState: StaticCallExecutionState,
lastStaticCallResult: SuccessfulEvmExecutionResult
): SolidityParameterType {
const values = convertEvmTupleToSolidityParam(lastStaticCallResult.values);
// TODO: We should have a way to handle other results.
return values[0];
return typeof exState.nameOrIndex === "string"
? (lastStaticCallResult.values.named[
exState.nameOrIndex
] as SolidityParameterType)
: (lastStaticCallResult.values.positional[
exState.nameOrIndex
] as SolidityParameterType);
}

export * from "./abi";
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export async function buildInitializeMessageFor(
deploymentParameters,
accounts
),
nameOrIndex: future.nameOrIndex,
functionName: future.functionName,
contractAddress: resolveAddressForContractFuture(
future.contract,
Expand Down Expand Up @@ -155,7 +156,7 @@ export async function buildInitializeMessageFor(
future.emitter,
future.eventName,
future.eventIndex,
future.argumentName,
future.nameOrIndex,
deploymentState,
deploymentLoader
);
Expand All @@ -168,7 +169,7 @@ export async function buildInitializeMessageFor(
{
artifactId: future.emitter.id,
eventName: future.eventName,
argumentName: future.argumentName,
nameOrIndex: future.nameOrIndex,
eventIndex: future.eventIndex,
txToReadFrom,
emitterAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export async function resolveReadEventArgumentResult(
emitter: ContractFuture<string>,
eventName: string,
eventIndex: number,
argumentName: string,
nameOrIndex: string | number,
deploymentState: DeploymentState,
deploymentLoader: DeploymentLoader
): Promise<{
Expand All @@ -211,7 +211,7 @@ export async function resolveReadEventArgumentResult(
emitterAddress,
eventName,
eventIndex,
argumentName
nameOrIndex
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function initialiseStaticCallExecutionStateFrom(
contractAddress: action.contractAddress,
functionName: action.functionName,
args: action.args,
nameOrIndex: action.nameOrIndex,
from: action.from,
networkInteractions: [],
};
Expand Down Expand Up @@ -94,7 +95,7 @@ export function initialiseReadEventArgumentExecutionStateFrom(
dependencies: new Set<string>(action.dependencies),
artifactId: action.artifactId,
eventName: action.eventName,
argumentName: action.argumentName,
nameOrIndex: action.nameOrIndex,
txToReadFrom: action.txToReadFrom,
emitterAddress: action.emitterAddress,
eventIndex: action.eventIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface StaticCallExecutionState
contractAddress: string;
functionName: string;
args: SolidityParameterType[];
nameOrIndex: string | number;
from: string;
networkInteractions: StaticCall[];
result?: StaticCallExecutionResult;
Expand Down Expand Up @@ -170,7 +171,7 @@ export interface ReadEventArgumentExecutionState
> {
artifactId: string;
eventName: string;
argumentName: string;
nameOrIndex: string | number;
txToReadFrom: string;
emitterAddress: string;
eventIndex: number;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/internal/execution/types/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface StaticCallExecutionStateInitializeMessage {
artifactId: string;
contractAddress: string;
functionName: string;
nameOrIndex: string | number;
args: SolidityParameterType[];
from: string;
}
Expand Down Expand Up @@ -165,7 +166,7 @@ export interface ReadEventArgExecutionStateInitializeMessage {
dependencies: string[];
artifactId: string;
eventName: string;
argumentName: string;
nameOrIndex: string | number;
txToReadFrom: string;
emitterAddress: string;
eventIndex: number;
Expand Down
19 changes: 16 additions & 3 deletions packages/core/src/internal/module-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class IgnitionModuleBuilderImplementation<
contractFuture: CallableContractFuture<ContractNameT>,
functionName: FunctionNameT,
args: ArgumentType[] = [],
nameOrIndex: string | number = 0,
options: StaticCallOptions = {}
): NamedStaticCallFuture<ContractNameT, FunctionNameT> {
const id = options.id ?? functionName;
Expand All @@ -388,6 +389,7 @@ class IgnitionModuleBuilderImplementation<
this._assertUniqueStaticCallId(futureId);
this._assertValidFrom(options.from, this.staticCall);
this._assertValidCallableContract(contractFuture, this.staticCall);
this._assertValidNameOrIndex(nameOrIndex, this.staticCall);
/* validation end */

const future = new NamedStaticCallFutureImplementation(
Expand All @@ -396,6 +398,7 @@ class IgnitionModuleBuilderImplementation<
functionName,
contractFuture,
args,
nameOrIndex,
options.from
);

Expand Down Expand Up @@ -496,7 +499,7 @@ class IgnitionModuleBuilderImplementation<
| SendDataFuture
| NamedContractCallFuture<string, string>,
eventName: string,
argumentName: string,
nameOrIndex: string | number,
options: ReadEventArgumentOptions = {}
): ReadEventArgumentFuture {
const eventIndex = options.eventIndex ?? 0;
Expand All @@ -521,20 +524,21 @@ class IgnitionModuleBuilderImplementation<

const id =
options.id ??
`${emitter.contractName}#${eventName}#${argumentName}#${eventIndex}`;
`${emitter.contractName}#${eventName}#${nameOrIndex}#${eventIndex}`;

const futureId = `${this._module.id}:${id}`;

/* validation start */
this._assertUniqueReadEventArgumentId(futureId);
this._assertValidNameOrIndex(nameOrIndex, this.readEventArgument);
/* validation end */

const future = new ReadEventArgumentFutureImplementation(
futureId,
this._module,
futureToReadFrom,
eventName,
argumentName,
nameOrIndex,
emitter,
eventIndex
);
Expand Down Expand Up @@ -778,6 +782,15 @@ class IgnitionModuleBuilderImplementation<
}
}

private _assertValidNameOrIndex(
nameOrIndex: string | number,
func: (...[]: any[]) => any
) {
if (typeof nameOrIndex !== "string" && typeof nameOrIndex !== "number") {
this._throwErrorWithStackTrace(`Invalid nameOrIndex given`, func);
}
}

private _assertValidAddress(
address:
| string
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/internal/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class NamedStaticCallFutureImplementation<
public readonly functionName: FunctionNameT,
public readonly contract: ContractFuture<ContractNameT>,
public readonly args: ArgumentType[],
public readonly nameOrIndex: string | number,
public readonly from: string | AccountRuntimeValue | undefined
) {
super(id, FutureType.NAMED_STATIC_CALL, module);
Expand Down Expand Up @@ -211,7 +212,7 @@ export class ReadEventArgumentFutureImplementation
| SendDataFuture
| NamedContractCallFuture<string, string>,
public readonly eventName: string,
public readonly argumentName: string,
public readonly nameOrIndex: string | number,
public readonly emitter: ContractFuture<string>,
public readonly eventIndex: number
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NamedStaticCallFuture } from "../../../types/module";
import { StaticCallExecutionState } from "../../execution/types/execution-state";
import { compare } from "../helpers/compare";
import { reconcileArguments } from "../helpers/reconcile-arguments";
import { reconcileContract } from "../helpers/reconcile-contract";
import { reconcileFrom } from "../helpers/reconcile-from";
Expand Down Expand Up @@ -31,5 +32,15 @@ export function reconcileNamedStaticCall(
return result;
}

result = compare(
future,
"Argument name or index",
executionState.nameOrIndex,
future.nameOrIndex
);
if (result !== undefined) {
return result;
}

return { success: true };
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export function reconcileReadEventArgument(

result = compare(
future,
"Argument name",
executionState.argumentName,
future.argumentName
"Argument name or index",
executionState.nameOrIndex,
future.nameOrIndex
);
if (result !== undefined) {
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { IgnitionValidationError } from "../../../errors";
import { isArtifactType } from "../../../type-guards";
import { ArtifactResolver } from "../../../types/artifact";
import { NamedStaticCallFuture } from "../../../types/module";
import { validateArtifactFunction } from "../../execution/abi";
import {
validateArtifactFunction,
validateFunctionArgumentParamType,
} from "../../execution/abi";

export async function validateNamedStaticCall(
future: NamedStaticCallFuture<string, string>,
Expand All @@ -26,4 +29,11 @@ export async function validateNamedStaticCall(
future.args,
true
);

validateFunctionArgumentParamType(
future.contract.contractName,
future.functionName,
artifact,
future.nameOrIndex
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export async function validateReadEventArgument(
validateArtifactEventArgumentParams(
artifact,
future.eventName,
future.argumentName
future.nameOrIndex
);
}
6 changes: 4 additions & 2 deletions packages/core/src/stored-deployment-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export class StoredDeploymentSerializer {
contract: this._convertFutureToFutureToken(future.contract),
functionName: future.functionName,
args: future.args.map((arg) => context.argReplacer(arg)),
nameOrIndex: future.nameOrIndex,
from: isRuntimeValue(future.from)
? this._serializeAccountRuntimeValue(future.from)
: future.from,
Expand Down Expand Up @@ -298,7 +299,7 @@ export class StoredDeploymentSerializer {
),
emitter: this._convertFutureToFutureToken(future.emitter),
eventName: future.eventName,
argumentName: future.argumentName,
nameOrIndex: future.nameOrIndex,
eventIndex: future.eventIndex,
};
return serializedReadEventArgumentFuture;
Expand Down Expand Up @@ -742,6 +743,7 @@ export class StoredDeploymentDeserializer {
serializedFuture.args.map((arg) =>
this._deserializeArgument(arg, futuresLookup)
),
serializedFuture.nameOrIndex,
this._isSerializedAccountRuntimeValue(serializedFuture.from)
? this._deserializeAccountRuntimeValue(serializedFuture.from)
: serializedFuture.from
Expand Down Expand Up @@ -795,7 +797,7 @@ export class StoredDeploymentDeserializer {
| ArtifactContractDeploymentFuture
| NamedContractCallFuture<string, string>,
serializedFuture.eventName,
serializedFuture.argumentName,
serializedFuture.nameOrIndex,
this._lookup(
contractFuturesLookup,
serializedFuture.emitter.futureId
Expand Down
Loading

0 comments on commit 768a9a8

Please sign in to comment.