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

Chainlink Functions contracts v1.3.0 #12481

Merged
merged 2 commits into from
Mar 31, 2024
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
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
Loading