-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Chainlink Functions contracts v1.3.0 (#12481)
* Chainlink Functions contracts v1.3.0 * Add changeset
- Loading branch information
1 parent
192fbfb
commit daa90db
Showing
9 changed files
with
1,552 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
440
contracts/src/v0.8/functions/v1_3_0/FunctionsBilling.sol
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
228
contracts/src/v0.8/functions/v1_3_0/FunctionsCoordinator.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.