Skip to content

Commit

Permalink
Chainlink Functions contracts v1.3.0 (#12481)
Browse files Browse the repository at this point in the history
* Chainlink Functions contracts v1.3.0

* Add changeset
  • Loading branch information
justinkaseman authored Mar 31, 2024
1 parent 192fbfb commit daa90db
Show file tree
Hide file tree
Showing 9 changed files with 1,552 additions and 0 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/afraid-seahorses-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": minor
---

Chainlink Functions contracts v1.3.0
440 changes: 440 additions & 0 deletions contracts/src/v0.8/functions/v1_3_0/FunctionsBilling.sol

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions contracts/src/v0.8/functions/v1_3_0/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 "../v1_0_0/interfaces/IFunctionsRouter.sol";
import {IFunctionsClient} from "../v1_0_0/interfaces/IFunctionsClient.sol";

import {FunctionsRequest} from "../v1_0_0/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_functionsRouter;

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

error OnlyRouterCanFulfill();

constructor(address router) {
i_functionsRouter = 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_functionsRouter.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_functionsRouter)) {
revert OnlyRouterCanFulfill();
}
_fulfillRequest(requestId, response, err);
emit RequestFulfilled(requestId);
}
}
228 changes: 228 additions & 0 deletions contracts/src/v0.8/functions/v1_3_0/FunctionsCoordinator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IFunctionsCoordinator} from "../v1_0_0/interfaces/IFunctionsCoordinator.sol";
import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";

import {FunctionsBilling, FunctionsBillingConfig} from "./FunctionsBilling.sol";
import {OCR2Base} from "./ocr/OCR2Base.sol";
import {FunctionsResponse} from "../v1_0_0/libraries/FunctionsResponse.sol";

/// @title Functions Coordinator contract
/// @notice Contract that nodes of a Decentralized Oracle Network (DON) interact with
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.3.0";

event OracleRequest(
bytes32 indexed requestId,
address indexed requestingContract,
address requestInitiator,
uint64 subscriptionId,
address subscriptionOwner,
bytes data,
uint16 dataVersion,
bytes32 flags,
uint64 callbackGasLimit,
FunctionsResponse.Commitment commitment
);
event OracleResponse(bytes32 indexed requestId, address transmitter);

error InconsistentReportData();
error EmptyPublicKey();
error UnauthorizedPublicKeyChange();

bytes private s_donPublicKey;
bytes private s_thresholdPublicKey;

constructor(
address router,
FunctionsBillingConfig memory config,
address linkToNativeFeed,
address linkToUsdFeed
) OCR2Base() FunctionsBilling(router, config, linkToNativeFeed, linkToUsdFeed) {}

/// @inheritdoc IFunctionsCoordinator
function getThresholdPublicKey() external view override returns (bytes memory) {
if (s_thresholdPublicKey.length == 0) {
revert EmptyPublicKey();
}
return s_thresholdPublicKey;
}

/// @inheritdoc IFunctionsCoordinator
function setThresholdPublicKey(bytes calldata thresholdPublicKey) external override onlyOwner {
if (thresholdPublicKey.length == 0) {
revert EmptyPublicKey();
}
s_thresholdPublicKey = thresholdPublicKey;
}

/// @inheritdoc IFunctionsCoordinator
function getDONPublicKey() external view override returns (bytes memory) {
if (s_donPublicKey.length == 0) {
revert EmptyPublicKey();
}
return s_donPublicKey;
}

/// @inheritdoc IFunctionsCoordinator
function setDONPublicKey(bytes calldata donPublicKey) external override onlyOwner {
if (donPublicKey.length == 0) {
revert EmptyPublicKey();
}
s_donPublicKey = donPublicKey;
}

/// @dev check if node is in current transmitter list
function _isTransmitter(address node) internal view returns (bool) {
// Bounded by "maxNumOracles" on OCR2Abstract.sol
for (uint256 i = 0; i < s_transmitters.length; ++i) {
if (s_transmitters[i] == node) {
return true;
}
}
return false;
}

/// @inheritdoc IFunctionsCoordinator
function startRequest(
FunctionsResponse.RequestMeta calldata request
) external override onlyRouter returns (FunctionsResponse.Commitment memory commitment) {
uint72 operationFee;
(commitment, operationFee) = _startBilling(request);

emit OracleRequest(
commitment.requestId,
request.requestingContract,
// solhint-disable-next-line avoid-tx-origin
tx.origin,
request.subscriptionId,
request.subscriptionOwner,
request.data,
request.dataVersion,
request.flags,
request.callbackGasLimit,
FunctionsResponse.Commitment({
coordinator: commitment.coordinator,
client: commitment.client,
subscriptionId: commitment.subscriptionId,
callbackGasLimit: commitment.callbackGasLimit,
estimatedTotalCostJuels: commitment.estimatedTotalCostJuels,
timeoutTimestamp: commitment.timeoutTimestamp,
requestId: commitment.requestId,
donFee: commitment.donFee,
gasOverheadBeforeCallback: commitment.gasOverheadBeforeCallback,
gasOverheadAfterCallback: commitment.gasOverheadAfterCallback,
// The following line is done to use the Coordinator's operationFee in place of the Router's operation fee
// With this in place the Router.adminFee must be set to 0 in the Router.
adminFee: operationFee
})
);

return commitment;
}

/// @dev DON fees are pooled together. If the OCR configuration is going to change, these need to be distributed.
function _beforeSetConfig(uint8 /* _f */, bytes memory /* _onchainConfig */) internal override {
if (_getTransmitters().length > 0) {
_disperseFeePool();
}
}

/// @dev Used by FunctionsBilling.sol
function _getTransmitters() internal view override returns (address[] memory) {
return s_transmitters;
}

function _beforeTransmit(
bytes calldata report
) internal view override returns (bool shouldStop, DecodedReport memory decodedReport) {
(
bytes32[] memory requestIds,
bytes[] memory results,
bytes[] memory errors,
bytes[] memory onchainMetadata,
bytes[] memory offchainMetadata
) = abi.decode(report, (bytes32[], bytes[], bytes[], bytes[], bytes[]));
uint256 numberOfFulfillments = uint8(requestIds.length);

if (
numberOfFulfillments == 0 ||
numberOfFulfillments != results.length ||
numberOfFulfillments != errors.length ||
numberOfFulfillments != onchainMetadata.length ||
numberOfFulfillments != offchainMetadata.length
) {
revert ReportInvalid("Fields must be equal length");
}

for (uint256 i = 0; i < numberOfFulfillments; ++i) {
if (_isExistingRequest(requestIds[i])) {
// If there is an existing request, validate report
// Leave shouldStop to default, false
break;
}
if (i == numberOfFulfillments - 1) {
// If the last fulfillment on the report does not exist, then all are duplicates
// Indicate that it's safe to stop to save on the gas of validating the report
shouldStop = true;
}
}

return (
shouldStop,
DecodedReport({
requestIds: requestIds,
results: results,
errors: errors,
onchainMetadata: onchainMetadata,
offchainMetadata: offchainMetadata
})
);
}

/// @dev Report hook called within OCR2Base.sol
function _report(DecodedReport memory decodedReport) internal override {
uint256 numberOfFulfillments = uint8(decodedReport.requestIds.length);

// Bounded by "MaxRequestBatchSize" on the Job's ReportingPluginConfig
for (uint256 i = 0; i < numberOfFulfillments; ++i) {
FunctionsResponse.FulfillResult result = FunctionsResponse.FulfillResult(
_fulfillAndBill(
decodedReport.requestIds[i],
decodedReport.results[i],
decodedReport.errors[i],
decodedReport.onchainMetadata[i],
decodedReport.offchainMetadata[i],
uint8(numberOfFulfillments) // will not exceed "MaxRequestBatchSize" on the Job's ReportingPluginConfig
)
);

// Emit on successfully processing the fulfillment
// In these two fulfillment results the user has been charged
// Otherwise, the DON will re-try
if (
result == FunctionsResponse.FulfillResult.FULFILLED ||
result == FunctionsResponse.FulfillResult.USER_CALLBACK_ERROR
) {
emit OracleResponse(decodedReport.requestIds[i], msg.sender);
}
}
}

/// @dev Used in FunctionsBilling.sol
function _onlyOwner() internal view override {
_validateOwnership();
}

/// @dev Used in FunctionsBilling.sol
function _owner() internal view override returns (address owner) {
return this.owner();
}
}
Loading

0 comments on commit daa90db

Please sign in to comment.