Skip to content

Commit 54c016b

Browse files
authored
Merge pull request #2 from ceptor-club/feature/functions_gw
Chainlink Functions | Gateway
2 parents 712e130 + f6707fd commit 54c016b

21 files changed

+5084
-3651
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4+
5+
### 0.0.2 (2024-05-22)
6+
7+
8+
### Features
9+
10+
* **hardhat:** 👻 Chainlink Contracts f94a6b6
11+
* **hardhat:** 👻 Chainlink Functions 2eaba29
12+
* **hardhat:** 👻 Functions Gateway 0dcce9b
13+
* **hardhat:** 👻 Gateway Clients 7e869c0
14+
* **hardhat:** 👻 Gateway Interfaces 4672e64

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "se-2",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"private": true,
55
"workspaces": {
66
"packages": [
@@ -32,11 +32,13 @@
3232
"postinstall": "husky install",
3333
"precommit": "lint-staged",
3434
"vercel": "yarn workspace @se-2/nextjs vercel",
35-
"vercel:yolo": "yarn workspace @se-2/nextjs vercel:yolo"
35+
"vercel:yolo": "yarn workspace @se-2/nextjs vercel:yolo",
36+
"release": "standard-version"
3637
},
3738
"packageManager": "[email protected]",
3839
"devDependencies": {
3940
"husky": "^8.0.1",
40-
"lint-staged": "^13.0.3"
41+
"lint-staged": "^13.0.3",
42+
"standard-version": "^9.5.0"
4143
}
4244
}

packages/hardhat/contracts/BuyMeACoffee.sol

-156
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import {ICCGatewayClient} from "./interfaces/ICCGatewayClient.sol";
5+
import {ICCGateway} from "./interfaces/ICCGateway.sol";
6+
7+
contract CCExampleClient is ICCGatewayClient {
8+
ICCGateway private immutable gateway;
9+
10+
uint64 private subscriptionId = 0;
11+
bytes private encryptedSecretsReference = "";
12+
13+
event ResponseReceived(uint64 subscriptionId, bytes32 requestId, string response);
14+
event ErrorReceived(uint64 subscriptionId, bytes32 requestId, string error);
15+
16+
constructor(address gatewayAddress) {
17+
gateway = ICCGateway(gatewayAddress);
18+
}
19+
20+
function updateSubscriptionId(uint64 _subscriptionId) external {
21+
subscriptionId = _subscriptionId;
22+
}
23+
24+
function updateEncryptedSecretsReference(bytes calldata _encryptedSecretsReference) external {
25+
encryptedSecretsReference = _encryptedSecretsReference;
26+
}
27+
28+
function request(string[] calldata args, bytes[] calldata bytesArgs) external {
29+
gateway.sendRequest(subscriptionId, args, bytesArgs, encryptedSecretsReference);
30+
}
31+
32+
function callback(bytes32 requestId) external override {
33+
ICCGatewayClient.CCGResponse memory response = gateway.getResponse(requestId, true);
34+
35+
if (response.state == ICCGatewayClient.CCGResponseState.Success) {
36+
emit ResponseReceived(response.subscriptionId, requestId, string(response.data));
37+
} else {
38+
emit ErrorReceived(response.subscriptionId, requestId, string(response.error));
39+
}
40+
}
41+
}
+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import './interfaces/ICCGateway.sol';
5+
import {ICCGatewayClient} from './interfaces/ICCGatewayClient.sol';
6+
7+
import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
8+
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
9+
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
10+
11+
contract CCGateway is ICCGateway, FunctionsClient, AccessControl {
12+
using FunctionsRequest for FunctionsRequest.Request;
13+
14+
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
15+
bytes32 public constant CLIENT_ROLE = keccak256("CLIENT_ROLE");
16+
bytes32 private _donId; // DON ID for the Functions DON to which the requests are sent
17+
18+
mapping(uint64 subscriptionId => CCGRequest) private _requests; // Each subscription can only handle one kind of request
19+
mapping(bytes32 requestId => ICCGatewayClient.CCGResponse) private unprocessed_responses; // Responses that have not been processed yet
20+
21+
constructor(address router, bytes32 initialDonId, address initialOwner) FunctionsClient(router) {
22+
_donId = initialDonId;
23+
24+
address to = address(0) == initialOwner ? _msgSender() : initialOwner;
25+
_grantRole(DEFAULT_ADMIN_ROLE, to);
26+
_grantRole(MANAGER_ROLE, to);
27+
}
28+
29+
/**
30+
* @dev Set the DON ID
31+
* @param newDonId New DON ID
32+
*/
33+
function setDonId(bytes32 newDonId) external onlyRole(getRoleAdmin(MANAGER_ROLE)) {
34+
_donId = newDonId;
35+
}
36+
37+
/**
38+
* @dev Get the DON ID
39+
* @return DON ID
40+
*/
41+
function donId() external view returns (bytes32) {
42+
return _donId;
43+
}
44+
45+
function registerRequest(
46+
uint64 subscriptionId,
47+
FunctionsRequest.Location codeLocation,
48+
string calldata source,
49+
FunctionsRequest.Location secretsLocation,
50+
bytes calldata encryptedSecretsReference,
51+
uint32 callbackGasLimit,
52+
string calldata name
53+
) external onlyRole(MANAGER_ROLE) {
54+
if(bytes(name).length == 0) {revert CCGRequestNameEmpty();}
55+
56+
CCGRequest storage req = _requests[subscriptionId];
57+
req.name = name;
58+
req.callbackGasLimit = callbackGasLimit;
59+
60+
// Only JavaScript is supported for now
61+
req.config.language = FunctionsRequest.CodeLanguage.JavaScript;
62+
req.config.codeLocation = codeLocation;
63+
req.config.source = source;
64+
req.config.secretsLocation = secretsLocation;
65+
req.config.encryptedSecretsReference = encryptedSecretsReference;
66+
}
67+
68+
69+
function getRequest(uint64 subscriptionId) external view onlyManager returns (CCGRequest memory) {
70+
if (bytes(_requests[subscriptionId].name).length == 0) {revert CCGRequestNotRegistered(subscriptionId);}
71+
return _requests[subscriptionId];
72+
}
73+
// ---------------------------------------------------------------------------------------------------------------------
74+
modifier onlySource(bytes32 requestId) {
75+
if (unprocessed_responses[requestId].source != _msgSender()) {revert CCGOnlySameSourceAllowed(requestId);}
76+
_;
77+
}
78+
79+
modifier onlyManager() {
80+
if (!hasRole(MANAGER_ROLE, tx.origin)) {revert CCGOnlyManagerAllowed();}
81+
_;
82+
}
83+
// ---------------------------------------------------------------------------------------------------------------------
84+
/**
85+
* @notice Triggers an on-demand Functions request using remote encrypted secrets
86+
* @param subscriptionId Subscription ID used to pay for request (FunctionsConsumer contract address must first be added to the subscription)
87+
* @param args String arguments passed into the source code and accessible via the global variable `args`
88+
* @param bytesArgs Bytes arguments passed into the source code and accessible via the global variable `bytesArgs` as hex strings
89+
* @param encryptedSecretsReference Reference pointing to encrypted secrets
90+
*/
91+
function sendRequest(
92+
uint64 subscriptionId,
93+
string[] calldata args,
94+
bytes[] calldata bytesArgs,
95+
bytes calldata encryptedSecretsReference
96+
) external onlyRole(CLIENT_ROLE) returns (bytes32 requestId) {
97+
CCGRequest storage request = _requests[subscriptionId];
98+
if (bytes(request.name).length == 0) {revert CCGRequestNotRegistered(subscriptionId);}
99+
100+
FunctionsRequest.Request memory req = request.config;
101+
req.initializeRequest(req.codeLocation, FunctionsRequest.CodeLanguage.JavaScript, req.source);
102+
if(encryptedSecretsReference.length > 0) {req.encryptedSecretsReference = encryptedSecretsReference;}
103+
104+
if (args.length > 0) {
105+
req.setArgs(args);
106+
}
107+
if (bytesArgs.length > 0) {
108+
req.setBytesArgs(bytesArgs);
109+
}
110+
111+
requestId = _sendRequest(req.encodeCBOR(), subscriptionId, request.callbackGasLimit, _donId);
112+
unprocessed_responses[requestId].state = ICCGatewayClient.CCGResponseState.Sent;
113+
unprocessed_responses[requestId].source = _msgSender();
114+
}
115+
116+
/**
117+
* @notice Store latest result/error
118+
* @param requestId The request ID, returned by sendRequest()
119+
* @param response Aggregated response from the user code
120+
* @param err Aggregated error from the user code or from the execution pipeline
121+
* Either response or error parameter will be set, but never both
122+
*/
123+
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
124+
ICCGatewayClient.CCGResponse storage resp = unprocessed_responses[requestId];
125+
if (resp.state != ICCGatewayClient.CCGResponseState.Sent) {revert CCGRequestAlreadyFulfilled(requestId);}
126+
127+
unprocessed_responses[requestId].state = err.length > 0 ? ICCGatewayClient.CCGResponseState.Error : ICCGatewayClient.CCGResponseState.Success;
128+
unprocessed_responses[requestId].data = response;
129+
unprocessed_responses[requestId].error = err;
130+
131+
// TODO: Call the client's callback function.
132+
ICCGatewayClient(resp.source).callback(requestId);
133+
}
134+
135+
/**
136+
* @dev Get the response data
137+
* @param requestId The request ID, returned by sendRequest()
138+
* @return response CCGResponse
139+
*/
140+
function getResponse(bytes32 requestId, bool remove) external onlySource(requestId) returns (ICCGatewayClient.CCGResponse memory response) {
141+
response = unprocessed_responses[requestId];
142+
if (remove) {delete unprocessed_responses[requestId];}
143+
}
144+
}

0 commit comments

Comments
 (0)