Skip to content

Commit

Permalink
(chore): Move Functions v1.0.0 to production (#10941)
Browse files Browse the repository at this point in the history
* Move Functions v0 code out of dev folder

* Rename Functions dev folder 1_X to reflect next version

* Add PR number

* Soothe linter errors for Functions 1.X
  • Loading branch information
justinkaseman authored Oct 16, 2023
1 parent 47902e1 commit a466aea
Show file tree
Hide file tree
Showing 67 changed files with 3,426 additions and 178 deletions.
1 change: 1 addition & 0 deletions contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Moved `VRFCoordinatorV2Mock.sol` to src/v0.8/vrf/mocks
- Moved `VRFCoordinatorMock.sol` to src/v0.8/vrf/mocks
- Release Functions v1.0.0 contracts. Start dev folder for v1.X (#10941)

### Removed

Expand Down
16 changes: 8 additions & 8 deletions contracts/scripts/native_solc_compile_all_functions
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ solc-select install $SOLC_VERSION
solc-select use $SOLC_VERSION
export SOLC_VERSION=$SOLC_VERSION

compileContract v1_0_0 dev/v1_0_0/libraries/FunctionsRequest.sol
compileContract v1_0_0 dev/v1_0_0/FunctionsRouter.sol
compileContract v1_0_0 dev/v1_0_0/FunctionsCoordinator.sol
compileContract v1_0_0 dev/v1_0_0/accessControl/TermsOfServiceAllowList.sol
compileContract v1_0_0 dev/v1_0_0/example/FunctionsClientExample.sol
compileContract v1_X dev/v1_X/libraries/FunctionsRequest.sol
compileContract v1_X dev/v1_X/FunctionsRouter.sol
compileContract v1_X dev/v1_X/FunctionsCoordinator.sol
compileContract v1_X dev/v1_X/accessControl/TermsOfServiceAllowList.sol
compileContract v1_X dev/v1_X/example/FunctionsClientExample.sol

# Test helpers
compileContract v1_0_0 tests/v1_0_0/testhelpers/FunctionsCoordinatorTestHelper.sol
compileContract v1_0_0 tests/v1_0_0/testhelpers/FunctionsLoadTestClient.sol
compileContract v1_X tests/v1_X/testhelpers/FunctionsCoordinatorTestHelper.sol
compileContract v1_X tests/v1_X/testhelpers/FunctionsLoadTestClient.sol

# Mocks
compileContract v1_0_0 dev/v1_0_0/mocks/FunctionsV1EventsMock.sol
compileContract v1_X dev/v1_X/mocks/FunctionsV1EventsMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {SafeCast} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/u

/// @title Functions Billing contract
/// @notice Contract that calculates payment from users to the nodes of the Decentralized Oracle Network (DON).
/// @dev THIS CONTRACT HAS NOT GONE THROUGH ANY SECURITY REVIEW. DO NOT USE IN PROD.
abstract contract FunctionsBilling is Routable, IFunctionsBilling {
using FunctionsResponse for FunctionsResponse.RequestMeta;
using FunctionsResponse for FunctionsResponse.Commitment;
Expand Down
62 changes: 62 additions & 0 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsClient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IFunctionsRouter} from "./interfaces/IFunctionsRouter.sol";
import {IFunctionsClient} from "./interfaces/IFunctionsClient.sol";

import {FunctionsRequest} from "./libraries/FunctionsRequest.sol";

/// @title The Chainlink Functions client contract
/// @notice Contract developers can inherit this contract in order to make Chainlink Functions requests
abstract contract FunctionsClient is IFunctionsClient {
using FunctionsRequest for FunctionsRequest.Request;

IFunctionsRouter internal immutable i_router;

event RequestSent(bytes32 indexed id);
event RequestFulfilled(bytes32 indexed id);

error OnlyRouterCanFulfill();

constructor(address router) {
i_router = IFunctionsRouter(router);
}

/// @notice Sends a Chainlink Functions request
/// @param data The CBOR encoded bytes data for a Functions request
/// @param subscriptionId The subscription ID that will be charged to service the request
/// @param callbackGasLimit the amount of gas that will be available for the fulfillment callback
/// @return requestId The generated request ID for this request
function _sendRequest(
bytes memory data,
uint64 subscriptionId,
uint32 callbackGasLimit,
bytes32 donId
) internal returns (bytes32) {
bytes32 requestId = i_router.sendRequest(
subscriptionId,
data,
FunctionsRequest.REQUEST_DATA_VERSION,
callbackGasLimit,
donId
);
emit RequestSent(requestId);
return requestId;
}

/// @notice User defined function to handle a response from the DON
/// @param requestId The request ID, returned by sendRequest()
/// @param response Aggregated response from the execution of the user's source code
/// @param err Aggregated error from the execution of the user code or from the execution pipeline
/// @dev Either response or error parameter will be set, but never both
function _fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal virtual;

/// @inheritdoc IFunctionsClient
function handleOracleFulfillment(bytes32 requestId, bytes memory response, bytes memory err) external override {
if (msg.sender != address(i_router)) {
revert OnlyRouterCanFulfill();
}
_fulfillRequest(requestId, response, err);
emit RequestFulfilled(requestId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {FunctionsResponse} from "./libraries/FunctionsResponse.sol";

/// @title Functions Coordinator contract
/// @notice Contract that nodes of a Decentralized Oracle Network (DON) interact with
/// @dev THIS CONTRACT HAS NOT GONE THROUGH ANY SECURITY REVIEW. DO NOT USE IN PROD.
contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilling {
using FunctionsResponse for FunctionsResponse.RequestMeta;
using FunctionsResponse for FunctionsResponse.Commitment;
using FunctionsResponse for FunctionsResponse.FulfillResult;

/// @inheritdoc ITypeAndVersion
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "Functions Coordinator v1.0.0";

event OracleRequest(
Expand Down Expand Up @@ -99,6 +99,7 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli
emit OracleRequest(
commitment.requestId,
request.requestingContract,
// solhint-disable-next-line avoid-tx-origin
tx.origin,
request.subscriptionId,
request.subscriptionOwner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
using FunctionsResponse for FunctionsResponse.Commitment;
using FunctionsResponse for FunctionsResponse.FulfillResult;

// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "Functions Router v1.0.0";

// We limit return data to a selector plus 4 words. This is to avoid
Expand Down Expand Up @@ -285,6 +286,7 @@ contract FunctionsRouter is IFunctionsRouter, FunctionsSubscriptions, Pausable,
subscriptionId: subscriptionId,
subscriptionOwner: subscription.owner,
requestingContract: msg.sender,
// solhint-disable-next-line avoid-tx-origin
requestInitiator: tx.origin,
data: data,
dataVersion: dataVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {SafeERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/

/// @title Functions Subscriptions contract
/// @notice Contract that coordinates payment from users to the nodes of the Decentralized Oracle Network (DON).
/// @dev THIS CONTRACT HAS NOT GONE THROUGH ANY SECURITY REVIEW. DO NOT USE IN PROD.
abstract contract FunctionsSubscriptions is IFunctionsSubscriptions, IERC677Receiver {
using SafeERC20 for IERC20;
using FunctionsResponse for FunctionsResponse.Commitment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ contract TermsOfServiceAllowList is ITermsOfServiceAllowList, IAccessController,
using EnumerableSet for EnumerableSet.AddressSet;

/// @inheritdoc ITypeAndVersion
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "Functions Terms of Service Allow List v1.0.0";

EnumerableSet.AddressSet private s_allowedSenders;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {FunctionsClient} from "../FunctionsClient.sol";
import {ConfirmedOwner} from "../../../../shared/access/ConfirmedOwner.sol";
import {FunctionsRequest} from "../libraries/FunctionsRequest.sol";

/// @title Chainlink Functions example Client contract implementation
contract FunctionsClientExample is FunctionsClient, ConfirmedOwner {
using FunctionsRequest for FunctionsRequest.Request;

uint32 public constant MAX_CALLBACK_GAS = 70_000;

bytes32 public s_lastRequestId;
bytes32 public s_lastResponse;
bytes32 public s_lastError;
uint32 public s_lastResponseLength;
uint32 public s_lastErrorLength;

error UnexpectedRequestID(bytes32 requestId);

constructor(address router) FunctionsClient(router) ConfirmedOwner(msg.sender) {}

/// @notice Send a simple request
/// @param source JavaScript source code
/// @param encryptedSecretsReferences Encrypted secrets payload
/// @param args List of arguments accessible from within the source code
/// @param subscriptionId Billing ID
function sendRequest(
string calldata source,
bytes calldata encryptedSecretsReferences,
string[] calldata args,
uint64 subscriptionId,
bytes32 jobId
) external onlyOwner {
FunctionsRequest.Request memory req;
req._initializeRequestForInlineJavaScript(source);
if (encryptedSecretsReferences.length > 0) req._addSecretsReference(encryptedSecretsReferences);
if (args.length > 0) req._setArgs(args);
s_lastRequestId = _sendRequest(req._encodeCBOR(), subscriptionId, MAX_CALLBACK_GAS, jobId);
}

/// @notice Store latest result/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
/// @dev Either response or error parameter will be set, but never both
function _fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
if (s_lastRequestId != requestId) {
revert UnexpectedRequestID(requestId);
}
// Save only the first 32 bytes of response/error to always fit within MAX_CALLBACK_GAS
s_lastResponse = _bytesToBytes32(response);
s_lastResponseLength = uint32(response.length);
s_lastError = _bytesToBytes32(err);
s_lastErrorLength = uint32(err.length);
}

function _bytesToBytes32(bytes memory b) private pure returns (bytes32 out) {
uint256 maxLen = 32;
if (b.length < 32) {
maxLen = b.length;
}
for (uint256 i = 0; i < maxLen; ++i) {
out |= bytes32(b[i]) >> (i * 8);
}
return out;
}
}
155 changes: 155 additions & 0 deletions contracts/src/v0.8/functions/dev/v1_X/libraries/FunctionsRequest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {CBOR} from "../../../../vendor/solidity-cborutils/v2.0.0/CBOR.sol";

/// @title Library for encoding the input data of a Functions request into CBOR
library FunctionsRequest {
using CBOR for CBOR.CBORBuffer;

uint16 public constant REQUEST_DATA_VERSION = 1;
uint256 internal constant DEFAULT_BUFFER_SIZE = 256;

enum Location {
Inline, // Provided within the Request
Remote, // Hosted through remote location that can be accessed through a provided URL
DONHosted // Hosted on the DON's storage
}

enum CodeLanguage {
JavaScript
// In future version we may add other languages
}

struct Request {
Location codeLocation; // ════════════╸ The location of the source code that will be executed on each node in the DON
Location secretsLocation; // ═════════╸ The location of secrets that will be passed into the source code. *Only Remote secrets are supported
CodeLanguage language; // ════════════╸ The coding language that the source code is written in
string source; // ════════════════════╸ Raw source code for Request.codeLocation of Location.Inline, URL for Request.codeLocation of Location.Remote, or slot decimal number for Request.codeLocation of Location.DONHosted
bytes encryptedSecretsReference; // ══╸ Encrypted URLs for Request.secretsLocation of Location.Remote (use addSecretsReference()), or CBOR encoded slotid+version for Request.secretsLocation of Location.DONHosted (use addDONHostedSecrets())
string[] args; // ════════════════════╸ String arguments that will be passed into the source code
bytes[] bytesArgs; // ════════════════╸ Bytes arguments that will be passed into the source code
}

error EmptySource();
error EmptySecrets();
error EmptyArgs();
error NoInlineSecrets();

/// @notice Encodes a Request to CBOR encoded bytes
/// @param self The request to encode
/// @return CBOR encoded bytes
function _encodeCBOR(Request memory self) internal pure returns (bytes memory) {
CBOR.CBORBuffer memory buffer = CBOR.create(DEFAULT_BUFFER_SIZE);

buffer.writeString("codeLocation");
buffer.writeUInt256(uint256(self.codeLocation));

buffer.writeString("language");
buffer.writeUInt256(uint256(self.language));

buffer.writeString("source");
buffer.writeString(self.source);

if (self.args.length > 0) {
buffer.writeString("args");
buffer.startArray();
for (uint256 i = 0; i < self.args.length; ++i) {
buffer.writeString(self.args[i]);
}
buffer.endSequence();
}

if (self.encryptedSecretsReference.length > 0) {
if (self.secretsLocation == Location.Inline) {
revert NoInlineSecrets();
}
buffer.writeString("secretsLocation");
buffer.writeUInt256(uint256(self.secretsLocation));
buffer.writeString("secrets");
buffer.writeBytes(self.encryptedSecretsReference);
}

if (self.bytesArgs.length > 0) {
buffer.writeString("bytesArgs");
buffer.startArray();
for (uint256 i = 0; i < self.bytesArgs.length; ++i) {
buffer.writeBytes(self.bytesArgs[i]);
}
buffer.endSequence();
}

return buffer.buf.buf;
}

/// @notice Initializes a Chainlink Functions Request
/// @dev Sets the codeLocation and code on the request
/// @param self The uninitialized request
/// @param codeLocation The user provided source code location
/// @param language The programming language of the user code
/// @param source The user provided source code or a url
function _initializeRequest(
Request memory self,
Location codeLocation,
CodeLanguage language,
string memory source
) internal pure {
if (bytes(source).length == 0) revert EmptySource();

self.codeLocation = codeLocation;
self.language = language;
self.source = source;
}

/// @notice Initializes a Chainlink Functions Request
/// @dev Simplified version of initializeRequest for PoC
/// @param self The uninitialized request
/// @param javaScriptSource The user provided JS code (must not be empty)
function _initializeRequestForInlineJavaScript(Request memory self, string memory javaScriptSource) internal pure {
_initializeRequest(self, Location.Inline, CodeLanguage.JavaScript, javaScriptSource);
}

/// @notice Adds Remote user encrypted secrets to a Request
/// @param self The initialized request
/// @param encryptedSecretsReference Encrypted comma-separated string of URLs pointing to off-chain secrets
function _addSecretsReference(Request memory self, bytes memory encryptedSecretsReference) internal pure {
if (encryptedSecretsReference.length == 0) revert EmptySecrets();

self.secretsLocation = Location.Remote;
self.encryptedSecretsReference = encryptedSecretsReference;
}

/// @notice Adds DON-hosted secrets reference to a Request
/// @param self The initialized request
/// @param slotID Slot ID of the user's secrets hosted on DON
/// @param version User data version (for the slotID)
function _addDONHostedSecrets(Request memory self, uint8 slotID, uint64 version) internal pure {
CBOR.CBORBuffer memory buffer = CBOR.create(DEFAULT_BUFFER_SIZE);

buffer.writeString("slotID");
buffer.writeUInt64(slotID);
buffer.writeString("version");
buffer.writeUInt64(version);

self.secretsLocation = Location.DONHosted;
self.encryptedSecretsReference = buffer.buf.buf;
}

/// @notice Sets args for the user run function
/// @param self The initialized request
/// @param args The array of string args (must not be empty)
function _setArgs(Request memory self, string[] memory args) internal pure {
if (args.length == 0) revert EmptyArgs();

self.args = args;
}

/// @notice Sets bytes args for the user run function
/// @param self The initialized request
/// @param args The array of bytes args (must not be empty)
function _setBytesArgs(Request memory self, bytes[] memory args) internal pure {
if (args.length == 0) revert EmptyArgs();

self.bytesArgs = args;
}
}
Loading

0 comments on commit a466aea

Please sign in to comment.