Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(chore): Move Functions v1.0.0 to production #10941

Merged
merged 8 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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