Chainlink Functions - Tutorial 1 Explained

Chainlink Functions - Tutorial 1 Explained

Request Computation

Chainlink has released the Chainlink Functions and its tutorials. Today, I will be explaining every code file used in the 1st tutorial.

You can refer to tutorial 1 by clicking HERE.

In tutorial 1, it is thoroughly explained how to request a computation using Chainlink Functions. In the tutorial, the computation that I will be explaining is the mean of a set of numbers.

Now, I will start explaining the different files used in the 1st tutorial.

Functions-request-config.js

const fs = require("fs")

// Loads environment variables from .env file (if it exists)
require("dotenv").config()

const Location = {
  Inline: 0,
  Remote: 1,
}

const CodeLanguage = {
  JavaScript: 0,
}

const ReturnType = {
  uint: "uint256",
  uint256: "uint256",
  int: "int256",
  int256: "int256",
  string: "string",
  bytes: "Buffer",
  Buffer: "Buffer",
}

// Configure the request by setting the fields below
const requestConfig = {
  // location of source code (only Inline is currently supported)
  codeLocation: Location.Inline,
  // location of secrets (Inline or Remote)
  secretsLocation: Location.Inline,
  // code language (only JavaScript is currently supported)
  codeLanguage: CodeLanguage.JavaScript,
  // string containing the source code to be executed
  source: fs.readFileSync("./Functions-request-source.js").toString(),
  // args can be accessed within the source code with `args[index]` (ie: args[0])
  args: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
  // expected type of the returned value
  expectedReturnType: ReturnType.uint256,
}

module.exports = requestConfig

The code above was taken from HERE. It is provided in the tutorial. It exports a configuration object requestConfig that specifies the parameters for the request to execute the Javascript Source Code used for finding the Geometric Mean.

Here is a breakdown of the code:

  • The fs module is imported to read the contents of a file.

  • The dotenv module is imported to load environment variables from a .env file if it exists.

  • Three constant objects are defined: Location, CodeLanguage, and ReturnType.

  • requestConfig is defined as an object with properties that include:

    • codeLocation: The location of the source code, either inline or remote. Currently, only inline is supported.

    • secretsLocation: The location of secrets, either inline or remote.

    • codeLanguage: The language of the source code, currently only JavaScript is supported.

    • source: The contents of the source code to be executed, read from a file.

    • args: An array of string arguments that can be accessed within the source code with args[index].

    • expectedReturnType: The expected type of the returned value.

Finally, the requestConfig object is exported so that it can be used by the Functions-request-source.js in the tutorial.

Note that the source code to be executed is read from the file Functions-request-source.js. The args property is an array of ten-string arguments that can be accessed within the source code. The expectedReturnType property specifies the expected type of the returned value.

Functions-request-source.js

// calculate geometric mean off-chain by a DON then return the result
// valures provided in args array

console.log(`calculate geometric mean of ${args}`)

// make sure arguments are provided
if (!args || args.length === 0) throw new Error("input not provided")

const product = args.reduce((accumulator, currentValue) => {
  const numValue = parseInt(currentValue)
  if (isNaN(numValue)) throw Error(`${currentValue} is not a number`)
  return accumulator * numValue
}, 1) // calculate the product of numbers provided in args array

const geometricMean = Math.pow(product, 1 / args.length) // geometric mean = length-root of (product)
console.log(`geometric mean is: ${geometricMean.toFixed(2)}`)

// Decimals are not handled in Solidity so multiply by 100 (for 2 decimals) and round to the nearest integer
// Functions.encodeUint256: Return a buffer from uint256
return Functions.encodeUint256(Math.round(geometricMean * 100))

The code above was taken from HERE. It is provided in the tutorial. The code calculates the geometric mean of an array of values and returns a buffer from a uint256 representation of the result. The code first prints a message to the console indicating that it is calculating the geometric mean of the provided arguments.

Next, it checks whether any arguments were provided, and throws an error if they were not. It then initializes a variable product to the product of the arguments by using the reduce() method to iterate over the args array, multiplying each element by the previous result. The parseInt() function is called to convert each element to a number, and an error is thrown if any element is not a number.

After calculating the product, the code computes the geometric mean by taking the args.length root of the product using Math.pow(). The result is then printed to the console with two decimal places using toFixed(), and multiplied by 100 and rounded to the nearest integer in preparation for encoding it into a uint256 buffer using the Functions.encodeUint256() function.

Overall, this code takes an array of values, calculates their geometric mean, and returns the result as a uint256 buffer. It also performs some error checking to ensure that the input is valid. It is not clear from the code what the context is for encoding the result as a uint256 buffer or what the Functions object represents.

FunctionsConsumer.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "./dev/functions/FunctionsClient.sol";
// import "@chainlink/contracts/src/v0.8/dev/functions/FunctionsClient.sol"; // Once published
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

/**
 * @title Functions Consumer contract
 * @notice This contract is a demonstration of using Functions.
 * @notice NOT FOR PRODUCTION USE
 */
contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
  using Functions for Functions.Request;

  bytes32 public latestRequestId;
  bytes public latestResponse;
  bytes public latestError;

  event OCRResponse(bytes32 indexed requestId, bytes result, bytes err);

  /**
   * @notice Executes once when a contract is created to initialize state variables
   *
   * @param oracle - The FunctionsOracle contract
   */
  constructor(address oracle) FunctionsClient(oracle) ConfirmedOwner(msg.sender) {}

  /**
   * @notice Send a simple request
   * 
   * @param source JavaScript source code
   * @param secrets Encrypted secrets payload
   * @param secretsLocation Location of encrypted secrets (0 for inline, 1 for remote)
   * @param args List of arguments accessible from within the source code
   * @param subscriptionId Funtions billing subscription ID
   * @param gasLimit Maximum amount of gas used to call the client contract's `handleOracleFulfillment` function
   * @return Functions request ID
   */
  function executeRequest(
    string calldata source,
    bytes calldata secrets,
    Functions.Location secretsLocation,
    string[] calldata args,
    uint64 subscriptionId,
    uint32 gasLimit
  ) public onlyOwner returns (bytes32) {
    Functions.Request memory req;
    req.initializeRequest(Functions.Location.Inline, Functions.CodeLanguage.JavaScript, source);
    if (secrets.length > 0) {
      if (secretsLocation == Functions.Location.Inline) {
        req.addInlineSecrets(secrets);
      } else {
        req.addRemoteSecrets(secrets);
      }
    }
    if (args.length > 0) req.addArgs(args);

    bytes32 assignedReqID = sendRequest(req, subscriptionId, gasLimit);
    latestRequestId = assignedReqID;
    return assignedReqID;
  }

  /**
   * @notice Callback that is invoked once the DON has resolved the request or hit an error
   *
   * @param requestId The request ID, returned by sendRequest()
   * @param response Aggregated response from the user code
   * @param err Aggregated error from the user code or from the execution pipeline
   * Either response or error parameter will be set, but never both
   */
  function fulfillRequest(
    bytes32 requestId,
    bytes memory response,
    bytes memory err
  ) internal override {
    latestResponse = response;
    latestError = err;
    emit OCRResponse(requestId, response, err);
  }

  /**
   * @notice Allows the Functions oracle address to be updated
   *
   * @param oracle New oracle address
   */
  function updateOracleAddress(address oracle) public onlyOwner {
    setOracle(oracle);
  }

  function addSimulatedRequestId(address oracleAddress, bytes32 requestId) public onlyOwner {
    addExternalRequest(oracleAddress, requestId);
  }
}

NOTE: This contract is labeled as a demonstration and is not intended for production use.

The code above was taken from HERE. It is provided in the tutorial. This is a Solidity smart contract that interacts with the Chainlink network to demonstrate how to use Chainlink Functions in a smart contract.

The contract starts with the SPDX-License-Identifier that specifies the license under which the contract code is available. In this case, it is the MIT license.

Next, it imports the FunctionsClient.sol file from the dev/functions directory and the ConfirmedOwner.sol file from the v0.8 directory of the Chainlink contracts.

The contract is named FunctionsConsumer and it inherits from both FunctionsClient and ConfirmedOwner contracts. The FunctionsClient contract provides the functionality to send requests to the Chainlink network, while the ConfirmedOwner contract restricts some of the functions to only the owner of the contract.

The contract contains several state variables, including latestRequestId, latestResponse, and latestError, which store the most recent request ID, response, and error, respectively. It also has an event OCRResponse that is emitted when a response or error is received from the Chainlink network.

The contract has a constructor that takes an address for the Chainlink Oracle contract and initializes the state variables using the constructor of the parent contracts.

The contract has a function executeRequest that sends a request to the Chainlink network and returns the request ID. The function takes several parameters, including the source code, encrypted secrets, arguments, subscription ID, and gas limit. The function first creates a new Functions.Request object and initializes it with the source code. It then adds the encrypted secrets and arguments to the request object if they are provided. Finally, it sends the request using the sendRequest function inherited from the FunctionsClient contract and returns the assigned request ID.

The contract has a function fulfillRequest that is called when a response or error is received from the Chainlink network. The function updates the state variables latestResponse and latestError and emits the OCRResponse event.

The contract also has a function updateOracleAddress that allows the owner to update the address of the Chainlink Oracle contract.

Finally, the contract has a function addSimulatedRequestId that allows the owner to add a simulated request ID for testing purposes.