Skip to content

Commit

Permalink
run functions simulations as purely a deno-runtime execution
Browse files Browse the repository at this point in the history
  • Loading branch information
koteld committed Jan 10, 2024
1 parent 38bc11e commit fd2e690
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 144 deletions.
15 changes: 1 addition & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,20 +224,7 @@ It is useful for debugging and for checking whether the source code you supply t
> **Note**
Install [Deno](https://deno.com/) and add it to PATH, run ```deno --version``` to verify installation. Instructions: [https://deno.com/#installation](https://deno.com/#installation).

Before you run Functions request simulations, you can configure it.
To achieve this, additional parameters have been included in the `chainlink` group of `hardhat.config.ts`:
```ts
module.exports = {
chainlink: {
functions_simulation: {
port, // Ganache local blockchain port, default: "8546"
}
},
...
}
```

Once these parameters are specified, Functions requests simulations could be performed following the [sandbox documentation](SANDBOX.md#service-alias-functionssimulation).
Functions' requests simulations could be performed following the [sandbox documentation](SANDBOX.md#service-alias-functionssimulation).

### Local testing
> **Note**
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"test:vrfCoordinator": "mocha --exit 'test/vrfCoordinator.test.ts'",
"test:automationRegistrar": "mocha --exit 'test/automationRegistrar.test.ts'",
"test:automationRegistry": "mocha --exit 'test/automationRegistry.test.ts'",
"test:functionsRouter": "mocha --exit 'test/functionsRouter.test.ts'",
"test:functions": "mocha --exit 'test/functionsRouter.test.ts'",
"test:functionsSimulation": "mocha --exit 'test/functionsSimulation.test.ts'",
"test": "yarn test:prepare && yarn test:dataFeed && yarn test:dataFeedProxy && yarn test:ensFeedsResolver && yarn test:feedRegistry && yarn test:l2FeedUptimeSequencer && yarn test:vrfCoordinator && yarn test:automationRegistrar && yarn test:automationRegistry && yarn test:functionsRouter",
"test": "yarn test:prepare && yarn test:dataFeed && yarn test:dataFeedProxy && yarn test:ensFeedsResolver && yarn test:feedRegistry && yarn test:l2FeedUptimeSequencer && yarn test:vrfCoordinator && yarn test:automationRegistrar && yarn test:automationRegistry && yarn test:functions",
"copyArtifacts": "copyfiles -a -f ./node_modules/@chainlink/contracts/abi/**/* chainlink-artifacts",
"removeArtifacts": "rm -rf chainlink-artifacts",
"compile": "hardhat compile",
Expand Down
44 changes: 18 additions & 26 deletions src/HardhatChainlink.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import { DecodedResult } from "@chainlink/functions-toolkit/dist/decodeResult";
import {
CodeLanguage,
FunctionsResponse,
GatewayResponse,
Location,
RequestCommitment,
ReturnType,
ThresholdPublicKey,
} from "@chainlink/functions-toolkit/dist/types";
import * as functionsToolkit from "@chainlink/functions-toolkit";
import "@nomiclabs/hardhat-ethers";
import { BigNumber, BigNumberish, BytesLike } from "ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
Expand Down Expand Up @@ -1184,7 +1175,7 @@ class Functions {

public timeoutRequests(
functionsRouterAddress: string,
requestCommitments: RequestCommitment[],
requestCommitments: functionsToolkit.RequestCommitment[],
overrides?: Overrides
): Promise<{ transactionHash: string }> {
return functions.timeoutRequests(
Expand Down Expand Up @@ -1220,7 +1211,7 @@ class Functions {
functionsRouterAddress: string,
requestId: string,
timeout?: number
): Promise<FunctionsResponse> {
): Promise<functionsToolkit.FunctionsResponse> {
return functions.listenForResponse(
this.hre,
functionsRouterAddress,
Expand All @@ -1235,7 +1226,7 @@ class Functions {
timeout?: number,
confirmations?: number,
checkInterval?: number
): Promise<FunctionsResponse> {
): Promise<functionsToolkit.FunctionsResponse> {
return functions.listenForResponseFromTransaction(
this.hre,
functionsRouterAddress,
Expand All @@ -1249,7 +1240,7 @@ class Functions {
public listenForResponses(
functionsRouterAddress: string,
subscriptionId: string,
callback: (functionsResponse: FunctionsResponse) => any
callback: (functionsResponse: functionsToolkit.FunctionsResponse) => any
): Promise<void> {
return functions.listenForResponses(
this.hre,
Expand All @@ -1274,7 +1265,7 @@ class Functions {
functionsRouterAddress: string,
donId: string
): Promise<{
thresholdPublicKey: ThresholdPublicKey;
thresholdPublicKey: functionsToolkit.ThresholdPublicKey;
donPublicKey: string;
}> {
return functions.fetchKeys(this.hre, functionsRouterAddress, donId);
Expand Down Expand Up @@ -1348,7 +1339,7 @@ class Functions {
donId: string,
gatewayUrls: string[]
): Promise<{
result: GatewayResponse;
result: functionsToolkit.GatewayResponse;
error?: string;
}> {
return functions.listDONHostedEncryptedSecrets(
Expand Down Expand Up @@ -1382,7 +1373,7 @@ class Functions {
toBlock?: number | "latest",
pastBlocksToSearch?: number,
overrides?: Overrides
): Promise<RequestCommitment> {
): Promise<functionsToolkit.RequestCommitment> {
return functions.fetchRequestCommitment(
this.hre,
functionsRouterAddress,
Expand Down Expand Up @@ -1440,10 +1431,10 @@ class Utils {
}

public async buildFunctionsRequestCBOR(
codeLocation: Location,
codeLanguage: CodeLanguage,
codeLocation: functionsToolkit.Location,
codeLanguage: functionsToolkit.CodeLanguage,
source: string,
secretsLocation?: Location,
secretsLocation?: functionsToolkit.Location,
encryptedSecretsReference?: string,
args?: string[],
bytesArgs?: string[]
Expand All @@ -1461,8 +1452,8 @@ class Utils {

public async decodeHexString(
resultHexstring: string,
expectedReturnType: ReturnType
): Promise<DecodedResult> {
expectedReturnType: functionsToolkit.ReturnType
): Promise<functionsToolkit.DecodedResult> {
return utils.decodeHexString(resultHexstring, expectedReturnType);
}
}
Expand Down Expand Up @@ -1633,12 +1624,13 @@ class FunctionsSimulation {

public async simulateRequest(
source: string,
args?: string[],
bytesArgs?: string[]
): Promise<DecodedResult> {
secrets: Record<string, string> | {},
args: string[] | [],
bytesArgs: string[] | []
): Promise<functionsToolkit.SimulationResult> {
return functionsSimulations.simulateRequest(
this.hre,
source,
secrets,
args,
bytesArgs
);
Expand Down
9 changes: 1 addition & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export interface ChainlinkUserConfig {
pg_password?: string;
pg_db?: string;
};
functions_simulation: {
port?: number;
};
}

// Add our types to the Hardhat config
Expand All @@ -45,8 +42,7 @@ declare module "hardhat/types/config" {

extendConfig(
(config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
const { confirmations, node, functions_simulation } =
userConfig.chainlink ?? {};
const { confirmations, node } = userConfig.chainlink ?? {};
config.chainlink = {
confirmations: confirmations || 1,
node: {
Expand All @@ -61,9 +57,6 @@ extendConfig(
pg_password: node?.pg_password,
pg_db: node?.pg_db,
},
functions_simulation: {
port: functions_simulation?.port,
},
};
}
);
Expand Down
86 changes: 7 additions & 79 deletions src/sandbox/functionsSimulations/index.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,15 @@
import * as functionsToolkit from "@chainlink/functions-toolkit";
import { DecodedResult } from "@chainlink/functions-toolkit/dist/decodeResult";
import { ReturnType } from "@chainlink/functions-toolkit/dist/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";

import { DEFAULT_PORT } from "../../shared/constants";
import * as functionsConsumer from "../functionsConsumer";

export const simulateRequest = async (
hre: HardhatRuntimeEnvironment,
source: string,
args?: string[],
bytesArgs?: string[]
): Promise<DecodedResult> => {
const simulationDeployment =
await functionsToolkit.startLocalFunctionsTestnet(
undefined,
{},
hre.config.chainlink.functions_simulation?.port || DEFAULT_PORT
);

const provider = new hre.ethers.providers.JsonRpcProvider(
`http://localhost:${
hre.config.chainlink.functions_simulation?.port || DEFAULT_PORT
}/`
);
const admin = new hre.ethers.Wallet(
simulationDeployment.adminWallet.privateKey,
provider
);
const functionsConsumerAddress = await functionsConsumer.deploy(
hre,
simulationDeployment.functionsRouterContract.address,
simulationDeployment.donId,
{
signer: admin,
provider,
}
);

const subscriptionManager =
await hre.chainlink.functions.initializeSubscriptionManager(
simulationDeployment.functionsRouterContract.address,
simulationDeployment.linkTokenContract.address,
{
signer: admin,
provider,
}
);

const responseListener =
await hre.chainlink.functions.initializeResponseListener(
simulationDeployment.functionsRouterContract.address,
{
signer: admin,
provider,
}
);

const { subscriptionId } = await subscriptionManager.createSubscription(
functionsConsumerAddress
);

const juelsAmount = hre.ethers.utils.parseUnits("100", "ether");
await subscriptionManager.fundSubscription(juelsAmount, subscriptionId);

const { transactionHash } = await functionsConsumer.sendRequest(
hre,
functionsConsumerAddress,
subscriptionId,
secrets: Record<string, string>,
args: string[],
bytesArgs: string[]
): Promise<functionsToolkit.SimulationResult> => {
return functionsToolkit.simulateScript({
source,
"0xabcd",
functionsToolkit.Location.Remote,
secrets,
args,
bytesArgs,
100_000
);

const receipt = await provider.getTransactionReceipt(transactionHash);
const requestId = receipt.logs[0].topics[1];
const response = await responseListener.listenForResponse(requestId);

return hre.chainlink.utils.decodeHexString(
response.responseBytesHexstring,
ReturnType.string
);
});
};
8 changes: 5 additions & 3 deletions src/tasks/sandbox/functionsSimulation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import * as functionsSimulations from "../../../sandbox/functionsSimulations";

export const simulateRequest: ActionType<{
source: string;
secrets?: string;
args?: string;
bytesArgs?: string;
}> = async (taskArgs, hre): Promise<string> => {
const secrets = taskArgs.secrets ? JSON.parse(taskArgs.secrets) : {};
const args = taskArgs.args
? taskArgs.args.split(",").map((value) => value.trim())
: [];
const bytesArgs = taskArgs.bytesArgs
? taskArgs.bytesArgs.split(",").map((value) => value.trim())
: [];
const decodedResult = await functionsSimulations.simulateRequest(
hre,
const simulationResult = await functionsSimulations.simulateRequest(
taskArgs.source,
secrets,
args,
bytesArgs
);
return decodedResult.toString();
return JSON.stringify(simulationResult);
};
6 changes: 1 addition & 5 deletions test/fixture-projects/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { HardhatUserConfig } from "hardhat/types";
import "../../../src/index";

const config: HardhatUserConfig = {
chainlink: {
functions_simulation: {
secrets: { test: "hello world" },
},
},
chainlink: {},
};

export default config;
26 changes: 19 additions & 7 deletions test/functionsSimulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,50 +17,62 @@ describe("Test chainlink:sandbox:functionsSimulation module [SKIP FOR GITHUB ACT
const result =
await this.hre.chainlink.sandbox.functionsSimulation.simulateRequest(
'return Functions.encodeString(secrets.test + " " + args[0] + " " + args[1] + bytesArgs[0] + bytesArgs[1])',
{ test: "hello" },
["hello", "world"],
["0x1234", "0x5678"]
);

expect(result).to.eq("hello world hello world0x12340x5678");
expect(result.capturedTerminalOutput).to.eq("");
expect(result.responseBytesHexstring).to.eq(
"0x68656c6c6f2068656c6c6f20776f726c64307831323334307835363738"
);
});
});

describe("Run methods as hre subtasks", function () {
it("Run functions simulation", async function () {
if (isGithubActions) this.skip();

const result = await this.hre.run(
const resultJSON = await this.hre.run(
`${PACKAGE_NAME}:${Task.functionsSimulation}:${FunctionsSimulationSubtask.simulateRequest}`,
{
source:
'return Functions.encodeString(secrets.test + " " + args[0] + " " + args[1] + bytesArgs[0] + bytesArgs[1])',
secrets: '{"test":"hello"}',
args: "hello, world",
bytesArgs: "0x1234, 0x5678",
}
);

expect(result).to.eq("hello world hello world0x12340x5678");
const result = JSON.parse(resultJSON);
expect(result.capturedTerminalOutput).to.eq("");
expect(result.responseBytesHexstring).to.eq(
"0x68656c6c6f2068656c6c6f20776f726c64307831323334307835363738"
);
});
});

describe("Run methods as subtasks of a hre task", function () {
it("Run functions simulation", async function () {
if (isGithubActions) this.skip();

const result = await this.hre.run(
const resultJSON = await this.hre.run(
`${PACKAGE_NAME}:${Task.functionsSimulation}`,
{
subtask: FunctionsSimulationSubtask.simulateRequest,
args: JSON.stringify({
source:
'return Functions.encodeString(secrets.test + " " + args[0] + " " + args[1] + bytesArgs[0] + bytesArgs[1])',
secrets: '{"test":"hello"}',
args: "hello, world",
bytesArgs: "0x1234, 0x5678",
}),
}
);

expect(result).to.eq("hello world hello world0x12340x5678");
const result = JSON.parse(resultJSON);
expect(result.capturedTerminalOutput).to.eq("");
expect(result.responseBytesHexstring).to.eq(
"0x68656c6c6f2068656c6c6f20776f726c64307831323334307835363738"
);
});
});
});

0 comments on commit fd2e690

Please sign in to comment.