Skip to content

Commit

Permalink
add function to encode a function call
Browse files Browse the repository at this point in the history
  • Loading branch information
zoeyTM committed May 16, 2024
1 parent 3551129 commit fb5ceca
Show file tree
Hide file tree
Showing 43 changed files with 1,907 additions and 29 deletions.
6 changes: 6 additions & 0 deletions examples/complete/contracts/BasicContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pragma solidity ^0.8.9;
// import "hardhat/console.sol";

contract BasicContract {
uint public savedArg;

event BasicEvent(uint eventArg);

receive() external payable {}
Expand All @@ -15,4 +17,8 @@ contract BasicContract {

emit BasicEvent(funcArg);
}

function otherFunction(uint arg) public payable {
savedArg = arg;
}
}
4 changes: 3 additions & 1 deletion examples/complete/ignition/modules/CompleteModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ module.exports = buildModule("CompleteModule", (m) => {
{ id: "ContractWithLibrary2" }
);

m.send("test_send", duplicate, 123n);
const data = m.encodeFunctionCall(duplicate, "otherFunction", [42n]);

m.send("test_send", duplicate, 123n, data);

return {
basic,
Expand Down
6 changes: 6 additions & 0 deletions examples/complete/test/Complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ describe("Complete", function () {

expect(await withLib.readonlyFunction(40)).to.equal(42);
});

it("Should call the otherFunction with 42", async () => {
const { duplicate } = await loadFixture(deployCompleteFixture);

expect(await duplicate.savedArg()).to.equal(42);
});
});
44 changes: 42 additions & 2 deletions packages/core/src/ignition-module-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NamedContractAtFutureImplementation,
NamedContractCallFutureImplementation,
NamedContractDeploymentFutureImplementation,
NamedEncodeFunctionCallFutureImplementation,
NamedLibraryDeploymentFutureImplementation,
NamedStaticCallFutureImplementation,
ReadEventArgumentFutureImplementation,
Expand All @@ -24,6 +25,7 @@ import {
isAccountRuntimeValue,
isAddressResolvableFuture,
isContractFuture,
isEncodeFunctionCallFuture,
isFuture,
isModuleParameterRuntimeValue,
isRuntimeValue,
Expand All @@ -35,6 +37,7 @@ import {
ContractCallFuture,
ContractDeploymentFuture,
ContractFuture,
EncodeFunctionCallFuture,
Future,
FutureType,
IgnitionModule,
Expand Down Expand Up @@ -63,6 +66,7 @@ import {
SerializedNamedContractAtFuture,
SerializedNamedContractCallFuture,
SerializedNamedContractDeploymentFuture,
SerializedNamedEncodeFunctionCallFuture,
SerializedNamedLibraryDeploymentFuture,
SerializedNamedStaticCallFuture,
SerializedReadEventArgumentFuture,
Expand Down Expand Up @@ -253,6 +257,21 @@ export class IgnitionModuleSerializer {
};
return serializedNamedStaticCallFuture;

case FutureType.ENCODE_FUNCTION_CALL:
const serializedEncodeFunctionCallFuture: SerializedNamedEncodeFunctionCallFuture =
{
id: future.id,
moduleId: future.module.id,
type: future.type,
dependencies: Array.from(future.dependencies).map((d) =>
this._convertFutureToFutureToken(d)
),
contract: this._convertFutureToFutureToken(future.contract),
functionName: future.functionName,
args: future.args.map((arg) => context.argReplacer(arg)),
};
return serializedEncodeFunctionCallFuture;

case FutureType.NAMED_ARTIFACT_CONTRACT_AT:
const serializedNamedContractAtFuture: SerializedNamedContractAtFuture =
{
Expand Down Expand Up @@ -329,7 +348,9 @@ export class IgnitionModuleSerializer {
value: isRuntimeValue(future.value)
? this._serializeModuleParamterRuntimeValue(future.value)
: this._serializeBigint(future.value),
data: future.data,
data: isEncodeFunctionCallFuture(future.data)
? this._convertFutureToFutureToken(future.data)
: future.data,
from: isRuntimeValue(future.from)
? this._serializeAccountRuntimeValue(future.from)
: future.from,
Expand Down Expand Up @@ -771,6 +792,19 @@ export class IgnitionModuleDeserializer {
? this._deserializeAccountRuntimeValue(serializedFuture.from)
: serializedFuture.from
);
case FutureType.ENCODE_FUNCTION_CALL:
return new NamedEncodeFunctionCallFutureImplementation(
serializedFuture.id,
mod,
serializedFuture.functionName,
this._lookup(
contractFuturesLookup,
serializedFuture.contract.futureId
),
serializedFuture.args.map((arg) =>
this._deserializeArgument(arg, futuresLookup)
)
);
case FutureType.NAMED_ARTIFACT_CONTRACT_AT:
return new NamedContractAtFutureImplementation(
serializedFuture.id,
Expand Down Expand Up @@ -848,7 +882,13 @@ export class IgnitionModuleDeserializer {
serializedFuture.value
) as ModuleParameterRuntimeValue<bigint>) // This is unsafe, but we only serialize valid values
: this._deserializedBigint(serializedFuture.value),
serializedFuture.data,
serializedFuture.data === undefined ||
typeof serializedFuture.data === "string"
? serializedFuture.data
: (this._lookup(
futuresLookup,
serializedFuture.data.futureId
) as EncodeFunctionCallFuture<string, string>),
this._isSerializedAccountRuntimeValue(serializedFuture.from)
? this._deserializeAccountRuntimeValue(serializedFuture.from)
: serializedFuture.from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ export class FutureProcessor {
assertIgnitionInvariant(
exState.type !== ExecutionSateType.CONTRACT_AT_EXECUTION_STATE &&
exState.type !==
ExecutionSateType.READ_EVENT_ARGUMENT_EXECUTION_STATE,
ExecutionSateType.READ_EVENT_ARGUMENT_EXECUTION_STATE &&
exState.type !==
ExecutionSateType.ENCODE_FUNCTION_CALL_EXECUTION_STATE,
`Unexpected ExectutionState ${exState.id} with type ${exState.type} and status ${exState.status}: it should have been immediately completed`
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CallExecutionStateInitializeMessage,
ContractAtExecutionStateInitializeMessage,
DeploymentExecutionStateInitializeMessage,
EncodeFunctionCallExecutionStateInitializeMessage,
JournalMessage,
JournalMessageType,
ReadEventArgExecutionStateInitializeMessage,
Expand All @@ -19,6 +20,8 @@ import {
resolveAddressForContractFuture,
resolveAddressLike,
resolveArgs,
resolveEncodeFunctionCallResult,
resolveFutureData,
resolveFutureFrom,
resolveLibraries,
resolveReadEventArgumentResult,
Expand Down Expand Up @@ -145,6 +148,37 @@ export async function buildInitializeMessageFor(

return namedStaticCallInit;
}
case FutureType.ENCODE_FUNCTION_CALL: {
const args = resolveArgs(
future.args,
deploymentState,
deploymentParameters,
accounts
);

const result = await resolveEncodeFunctionCallResult(
future.contract.id,
future.functionName,
args,
deploymentLoader
);

const encodeFunctionCallInit: EncodeFunctionCallExecutionStateInitializeMessage =
_extendBaseInitWith(
JournalMessageType.ENCODE_FUNCTION_CALL_EXECUTION_STATE_INITIALIZE,
future,
strategy.name,
strategy.config,
{
args,
functionName: future.functionName,
artifactId: future.contract.id,
result,
}
);

return encodeFunctionCallInit;
}
case FutureType.NAMED_ARTIFACT_CONTRACT_AT:
case FutureType.CONTRACT_AT: {
const contractAtInit: ContractAtExecutionStateInitializeMessage =
Expand Down Expand Up @@ -219,7 +253,7 @@ export async function buildInitializeMessageFor(
deploymentState,
accounts
),
data: future.data ?? "0x",
data: resolveFutureData(future.data, deploymentState),
from: resolveFutureFrom(future.from, accounts, defaultSender),
}
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { isAddress } from "ethers";

import {
isAccountRuntimeValue,
isFuture,
Expand All @@ -11,6 +9,7 @@ import {
AddressResolvableFuture,
ArgumentType,
ContractFuture,
EncodeFunctionCallFuture,
Future,
ModuleParameterRuntimeValue,
ReadEventArgumentFuture,
Expand Down Expand Up @@ -117,6 +116,25 @@ export function resolveFutureFrom(
return resolveAccountRuntimeValue(from, accounts);
}

/**
* Resolve a `send` future's data parameter to a string.
*/
export function resolveFutureData(
data: string | EncodeFunctionCallFuture<string, string> | undefined,
deploymentState: DeploymentState
): string {
if (data === undefined) {
return "0x";
}

if (typeof data === "string") {
return data;
}

// this type coercion is safe because we know the type of data is EncodeFunctionCallFuture
return findResultForFutureById(deploymentState, data.id) as string;
}

/**
* Resolves an account runtime value to an address.
*/
Expand Down Expand Up @@ -221,6 +239,7 @@ export function resolveAddressLike(

const result = findResultForFutureById(deploymentState, addressLike.id);

const { isAddress } = require("ethers") as typeof import("ethers");
assertIgnitionInvariant(
typeof result === "string" && isAddress(result),
`Future '${addressLike.id}' must be a valid address`
Expand Down Expand Up @@ -272,3 +291,17 @@ export async function resolveReadEventArgumentResult(
txToReadFrom: confirmedTx.hash,
};
}

export async function resolveEncodeFunctionCallResult(
artifactId: string,
functionName: string,
args: SolidityParameterType[],
deploymentLoader: DeploymentLoader
): Promise<string> {
const artifact = await deploymentLoader.loadArtifact(artifactId);

const { Interface } = require("ethers") as typeof import("ethers");
const iface = new Interface(artifact.abi);

return iface.encodeFunctionData(functionName, args);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export async function saveArtifactsForFuture(
);
case FutureType.CONTRACT_CALL:
case FutureType.STATIC_CALL:
case FutureType.ENCODE_FUNCTION_CALL:
case FutureType.READ_EVENT_ARGUMENT:
case FutureType.SEND_DATA:
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import uniq from "lodash/uniq";
import { IgnitionError } from "../../../errors";
import {
isArtifactContractAtFuture,
isEncodeFunctionCallFuture,
isNamedContractAtFuture,
isReadEventArgumentFuture,
} from "../../../type-guards";
Expand Down Expand Up @@ -251,7 +252,9 @@ function createMapFromSenderToNonceAndTransactions(
if (
executionState.type ===
ExecutionSateType.READ_EVENT_ARGUMENT_EXECUTION_STATE ||
executionState.type === ExecutionSateType.CONTRACT_AT_EXECUTION_STATE
executionState.type === ExecutionSateType.CONTRACT_AT_EXECUTION_STATE ||
executionState.type ===
ExecutionSateType.ENCODE_FUNCTION_CALL_EXECUTION_STATE
) {
continue;
}
Expand Down Expand Up @@ -342,5 +345,9 @@ function _pickFrom(
return null;
}

if (isEncodeFunctionCallFuture(future)) {
return null;
}

return resolveFutureFrom(future.from, accounts, defaultSender);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ContractAtExecutionStateInitializeMessage,
DeploymentExecutionStateCompleteMessage,
DeploymentExecutionStateInitializeMessage,
EncodeFunctionCallExecutionStateInitializeMessage,
JournalMessage,
JournalMessageType,
NetworkInteractionRequestMessage,
Expand All @@ -29,6 +30,7 @@ import {
initialiseCallExecutionStateFrom,
initialiseContractAtExecutionStateFrom,
initialiseDeploymentExecutionStateFrom,
initialiseEncodeFunctionCallExecutionStateFrom,
initialiseReadEventArgumentExecutionStateFrom,
initialiseSendDataExecutionStateFrom,
initialiseStaticCallExecutionStateFrom,
Expand Down Expand Up @@ -77,6 +79,7 @@ export function executionStateReducer(
| SendDataExecutionStateCompleteMessage
| ContractAtExecutionStateInitializeMessage
| ReadEventArgExecutionStateInitializeMessage
| EncodeFunctionCallExecutionStateInitializeMessage
| NetworkInteractionRequestMessage
| TransactionSendMessage
| TransactionConfirmMessage
Expand All @@ -99,6 +102,8 @@ export function executionStateReducer(
return initialiseContractAtExecutionStateFrom(action);
case JournalMessageType.READ_EVENT_ARGUMENT_EXECUTION_STATE_INITIALIZE:
return initialiseReadEventArgumentExecutionStateFrom(action);
case JournalMessageType.ENCODE_FUNCTION_CALL_EXECUTION_STATE_INITIALIZE:
return initialiseEncodeFunctionCallExecutionStateFrom(action);
case JournalMessageType.DEPLOYMENT_EXECUTION_STATE_COMPLETE:
return _ensureStateThen(
state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CallExecutionState,
ContractAtExecutionState,
DeploymentExecutionState,
EncodeFunctionCallExecutionState,
ExecutionSateType,
ExecutionStatus,
ReadEventArgumentExecutionState,
Expand All @@ -13,6 +14,7 @@ import {
CallExecutionStateInitializeMessage,
ContractAtExecutionStateInitializeMessage,
DeploymentExecutionStateInitializeMessage,
EncodeFunctionCallExecutionStateInitializeMessage,
ReadEventArgExecutionStateInitializeMessage,
SendDataExecutionStateInitializeMessage,
StaticCallExecutionStateInitializeMessage,
Expand Down Expand Up @@ -128,6 +130,27 @@ export function initialiseContractAtExecutionStateFrom(
return contractAtExecutionInitialState;
}

export function initialiseEncodeFunctionCallExecutionStateFrom(
action: EncodeFunctionCallExecutionStateInitializeMessage
): EncodeFunctionCallExecutionState {
const encodeFunctionCallExecutionInitialState: EncodeFunctionCallExecutionState =
{
id: action.futureId,
type: ExecutionSateType.ENCODE_FUNCTION_CALL_EXECUTION_STATE,
futureType: FutureType.ENCODE_FUNCTION_CALL,
strategy: action.strategy,
strategyConfig: action.strategyConfig,
status: ExecutionStatus.SUCCESS,
dependencies: new Set<string>(action.dependencies),
artifactId: action.artifactId,
functionName: action.functionName,
args: action.args,
result: action.result,
};

return encodeFunctionCallExecutionInitialState;
}

export function initialiseCallExecutionStateFrom(
action: CallExecutionStateInitializeMessage
): CallExecutionState {
Expand Down
Loading

0 comments on commit fb5ceca

Please sign in to comment.