Skip to content

Commit 6aa9066

Browse files
authored
View function for fetching non-expired allowlisted request digests (#196)
* View function for fetching non-expired allowlisted request digests * View functions for pre-fetching allowlisted requests * One more comment * Updated timestamp type in error/event, removed inlined function
1 parent b86a78b commit 6aa9066

File tree

7 files changed

+435
-176
lines changed

7 files changed

+435
-176
lines changed

contracts/gas-snapshots/workflow.gas-snapshot

Lines changed: 157 additions & 155 deletions
Large diffs are not rendered by default.

contracts/src/v0.8/workflow/dev/v2/WorkflowRegistry.sol

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
9494
mapping(bytes32 rid => bytes32 donHash) private s_donByWorkflowRid;
9595

9696
/// @dev Tracking allowlisted requests for the owner address, required to enable anyone to verify off-chain requests.
97-
mapping(address owner => mapping(bytes32 requestDigest => uint256 expiryTimestamp)) private s_requestsAllowlist;
97+
mapping(bytes32 ownerDigestHash => uint32 expiryTimestamp) private s_requestsAllowlist;
98+
/// @dev Storing allowlisted requests for all owners, enabling fetching all non-expired requests
99+
OwnerAllowlistedRequest[] private s_requestAllowlistArray;
98100
/// @dev Map each owner address to their arbitrary config. Can be used to control billing parameters or any other data per owner
99101
mapping(address owner => bytes config) private s_ownerConfig;
100102

@@ -136,7 +138,7 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
136138
event WorkflowDonFamilyUpdated(
137139
bytes32 indexed workflowId, address indexed owner, string oldDonFamily, string newDonFamily
138140
);
139-
event RequestAllowlisted(address indexed owner, bytes32 indexed requestDigest, uint256 expiryTimestamp);
141+
event RequestAllowlisted(address indexed owner, bytes32 indexed requestDigest, uint32 expiryTimestamp);
140142
/// @notice Emitted when metadata length limits are updated
141143
event MetadataConfigUpdated(uint8 maxNameLen, uint8 maxTagLen, uint8 maxUrlLen, uint16 maxAttrLen);
142144
/// @notice Emitted when a workflow owner’s config is updated
@@ -172,7 +174,7 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
172174
error EmptyUpdateBatch();
173175
error BinaryURLRequired();
174176
error CannotUpdateDONFamilyForPausedWorkflows();
175-
error RequestExpired(bytes32 requestDigest, uint256 expiryTimestamp);
177+
error RequestExpired(bytes32 requestDigest, uint32 expiryTimestamp);
176178

177179
// ================================================================
178180
// | Enums |
@@ -279,6 +281,13 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
279281
bytes payload; // ABI‑encoded event data
280282
}
281283

284+
/// @dev Struct for OwnerAllowlistedRequest. This is used to return the allowlisted request data for each owner.
285+
struct OwnerAllowlistedRequest {
286+
bytes32 requestDigest;
287+
address owner;
288+
uint32 expiryTimestamp;
289+
}
290+
282291
// ================================================================
283292
// | Workflow Metadata Config |
284293
// ================================================================
@@ -1312,11 +1321,14 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
13121321
/// - User calls allowlistRequest(requestDigest) on the Workflow Registry.
13131322
/// - Any user can then send the request payload to the Vault DON.
13141323
/// - Vault DON checks if the digest is on-chain for verification purposes.
1315-
function allowlistRequest(bytes32 requestDigest, uint256 expiryTimestamp) external {
1324+
function allowlistRequest(bytes32 requestDigest, uint32 expiryTimestamp) external {
13161325
if (expiryTimestamp <= block.timestamp) revert RequestExpired(requestDigest, expiryTimestamp);
13171326
if (!s_linkedOwners.contains(msg.sender)) revert OwnershipLinkDoesNotExist(msg.sender);
13181327

1319-
s_requestsAllowlist[msg.sender][requestDigest] = expiryTimestamp;
1328+
s_requestsAllowlist[keccak256(abi.encode(msg.sender, requestDigest))] = expiryTimestamp;
1329+
s_requestAllowlistArray.push(
1330+
OwnerAllowlistedRequest({owner: msg.sender, requestDigest: requestDigest, expiryTimestamp: expiryTimestamp})
1331+
);
13201332
emit RequestAllowlisted(msg.sender, requestDigest, expiryTimestamp);
13211333
}
13221334

@@ -1327,7 +1339,41 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
13271339
/// @param requestDigest Unique identifier for the request (hash of the request payload).
13281340
/// @return bool True if the request is allowlisted and not expired, false otherwise.
13291341
function isRequestAllowlisted(address owner, bytes32 requestDigest) external view returns (bool) {
1330-
return s_requestsAllowlist[owner][requestDigest] > block.timestamp;
1342+
return s_requestsAllowlist[keccak256(abi.encode(owner, requestDigest))] > block.timestamp;
1343+
}
1344+
1345+
function getAllowlistedRequests(
1346+
uint256 start,
1347+
uint256 limit
1348+
) external view returns (OwnerAllowlistedRequest[] memory allowlistedRequests) {
1349+
uint256 total = s_requestAllowlistArray.length;
1350+
uint256 pageCount = _getPageCount(total, start, limit);
1351+
1352+
if (pageCount == 0) return new OwnerAllowlistedRequest[](0);
1353+
1354+
allowlistedRequests = new OwnerAllowlistedRequest[](pageCount);
1355+
uint256 addedCount = 0;
1356+
for (uint256 i = 0; i < pageCount; ++i) {
1357+
OwnerAllowlistedRequest storage request = s_requestAllowlistArray[start + i];
1358+
if (request.expiryTimestamp > block.timestamp) {
1359+
allowlistedRequests[addedCount] = request;
1360+
++addedCount;
1361+
}
1362+
}
1363+
1364+
if (addedCount < pageCount) {
1365+
OwnerAllowlistedRequest[] memory shrinkedList = new OwnerAllowlistedRequest[](addedCount);
1366+
for (uint256 i = 0; i < addedCount; ++i) {
1367+
shrinkedList[i] = allowlistedRequests[i];
1368+
}
1369+
allowlistedRequests = shrinkedList;
1370+
}
1371+
1372+
return allowlistedRequests;
1373+
}
1374+
1375+
function totalAllowlistedRequests() external view returns (uint256) {
1376+
return s_requestAllowlistArray.length;
13311377
}
13321378

13331379
// ================================================================

contracts/src/v0.8/workflow/dev/v2/test/WorkflowRegistry/WorkflowRegistry.allowlistRequest.t.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ contract WorkflowRegistry_allowlistRequest is WorkflowRegistrySetup {
88
function test_allowlistRequest_WhenTheUserIsNotLinked() external {
99
// it should revert with OwnershipLinkDoesNotExist
1010
bytes32 requestDigest = keccak256("request-digest");
11-
uint256 expiryTimestamp = block.timestamp + 1 hours;
11+
uint32 expiryTimestamp = uint32(block.timestamp + 1 hours);
1212

1313
address vaultNode = address(0x89652);
1414
vm.prank(vaultNode);
@@ -19,13 +19,13 @@ contract WorkflowRegistry_allowlistRequest is WorkflowRegistrySetup {
1919
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
2020

2121
// old timestamp should revert
22-
expiryTimestamp = block.timestamp - 1 hours;
22+
expiryTimestamp = uint32(block.timestamp - 1 hours);
2323
vm.expectRevert(abi.encodeWithSelector(WorkflowRegistry.RequestExpired.selector, requestDigest, expiryTimestamp));
2424
vm.prank(s_user);
2525
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
2626

2727
// timestamp equal to current block timestamp should revert
28-
expiryTimestamp = block.timestamp;
28+
expiryTimestamp = uint32(block.timestamp);
2929
vm.expectRevert(abi.encodeWithSelector(WorkflowRegistry.RequestExpired.selector, requestDigest, expiryTimestamp));
3030
vm.prank(s_user);
3131
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
@@ -34,7 +34,7 @@ contract WorkflowRegistry_allowlistRequest is WorkflowRegistrySetup {
3434
function test_allowlistRequest_WhenTheUserIsLinked() external {
3535
//it should allowlist the request digest
3636
bytes32 requestDigest = keccak256("request-digest");
37-
uint256 expiryTimestamp = block.timestamp + 1 hours;
37+
uint32 expiryTimestamp = uint32(block.timestamp + 1 hours);
3838

3939
// link the owner first to ensure the request can be allowlisted
4040
_linkOwner(s_user);
@@ -51,7 +51,7 @@ contract WorkflowRegistry_allowlistRequest is WorkflowRegistrySetup {
5151
assertTrue(s_registry.isRequestAllowlisted(s_user, requestDigest), "Request should be allowlisted");
5252

5353
bytes32 newRequestDigest = keccak256("new-request-digest");
54-
uint256 newExpiryTimestamp = block.timestamp + 1 hours; // same timestamp as the previous request
54+
uint32 newExpiryTimestamp = uint32(block.timestamp + 1 hours); // same timestamp as the previous request
5555
vm.expectEmit(true, true, true, false);
5656
emit WorkflowRegistry.RequestAllowlisted(s_user, newRequestDigest, newExpiryTimestamp);
5757
vm.prank(s_user);
@@ -66,7 +66,7 @@ contract WorkflowRegistry_allowlistRequest is WorkflowRegistrySetup {
6666
assertFalse(s_registry.isRequestAllowlisted(s_user, newRequestDigest), "New request should expire");
6767
assertFalse(s_registry.isRequestAllowlisted(s_user, requestDigest), "Old request should expire");
6868

69-
newExpiryTimestamp = block.timestamp + 2 hours; // same digest, but one hour ahead of block time
69+
newExpiryTimestamp = uint32(block.timestamp + 2 hours); // same digest, but one hour ahead of block time
7070
vm.expectEmit(true, true, true, false);
7171
emit WorkflowRegistry.RequestAllowlisted(s_user, newRequestDigest, newExpiryTimestamp);
7272
vm.prank(s_user);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.26;
3+
4+
import {WorkflowRegistry} from "../../WorkflowRegistry.sol";
5+
6+
import {WorkflowRegistrySetup} from "./WorkflowRegistrySetup.t.sol";
7+
8+
contract WorkflowRegistry_getAllowlistedRequests is WorkflowRegistrySetup {
9+
// NOTE: lock and control current timestamp this way due to issues when via-ir is enabled:
10+
// https://github.com/foundry-rs/foundry/issues/1373
11+
uint256 public currentTimestamp = block.timestamp;
12+
13+
function test_getAllowlistedRequests_WhenNoRequestsAreAllowlisted() external {
14+
// it should return an empty array
15+
uint256 total = s_registry.totalAllowlistedRequests();
16+
WorkflowRegistry.OwnerAllowlistedRequest[] memory requests = s_registry.getAllowlistedRequests(0, 100);
17+
assertEq(total, 0, "Total number of allowlisted requests should be 0");
18+
assertEq(requests.length, 0, "Zero requests should be returned");
19+
}
20+
21+
modifier whenSomeRequestsAreAllowlisted() {
22+
_;
23+
}
24+
25+
function test_getAllowlistedRequests_WhenNoneOfTheRequestsHaveExpired() external whenSomeRequestsAreAllowlisted {
26+
// it should return all requests
27+
_linkTestOwners();
28+
_allowlistValidTestRequests();
29+
30+
uint256 total = s_registry.totalAllowlistedRequests();
31+
WorkflowRegistry.OwnerAllowlistedRequest[] memory requests = s_registry.getAllowlistedRequests(0, 100);
32+
assertEq(total, 6, "Total number of allowlisted requests should be 6");
33+
assertEq(requests.length, 6, "All 6 requests should be returned");
34+
assertEq(keccak256("request-digest-1-owner-1"), requests[0].requestDigest, "First request digest should match");
35+
assertEq(keccak256("request-digest-2-owner-1"), requests[1].requestDigest, "Second request digest should match");
36+
assertEq(keccak256("request-digest-1-owner-2"), requests[2].requestDigest, "Third request digest should match");
37+
assertEq(keccak256("request-digest-1-owner-3"), requests[3].requestDigest, "Fourth request digest should match");
38+
assertEq(keccak256("request-digest-2-owner-3"), requests[4].requestDigest, "Fifth request digest should match");
39+
assertEq(keccak256("request-digest-3-owner-3"), requests[5].requestDigest, "Sixth request digest should match");
40+
41+
// try out pagination - page size 2
42+
requests = s_registry.getAllowlistedRequests(0, 2);
43+
assertEq(requests.length, 2, "2 requests should be returned");
44+
assertEq(keccak256("request-digest-1-owner-1"), requests[0].requestDigest, "First request digest should match");
45+
assertEq(keccak256("request-digest-2-owner-1"), requests[1].requestDigest, "Second request digest should match");
46+
47+
requests = s_registry.getAllowlistedRequests(2, 2);
48+
assertEq(requests.length, 2, "2 requests should be returned");
49+
assertEq(keccak256("request-digest-1-owner-2"), requests[0].requestDigest, "Third request digest should match");
50+
assertEq(keccak256("request-digest-1-owner-3"), requests[1].requestDigest, "Fourth request digest should match");
51+
52+
requests = s_registry.getAllowlistedRequests(4, 2);
53+
assertEq(requests.length, 2, "2 requests should be returned");
54+
assertEq(keccak256("request-digest-2-owner-3"), requests[0].requestDigest, "Fifth request digest should match");
55+
assertEq(keccak256("request-digest-3-owner-3"), requests[1].requestDigest, "Sixth request digest should match");
56+
57+
// try out pagination - page size 4
58+
requests = s_registry.getAllowlistedRequests(0, 4);
59+
assertEq(requests.length, 4, "4 requests should be returned");
60+
assertEq(keccak256("request-digest-1-owner-1"), requests[0].requestDigest, "First request digest should match");
61+
assertEq(keccak256("request-digest-2-owner-1"), requests[1].requestDigest, "Second request digest should match");
62+
assertEq(keccak256("request-digest-1-owner-2"), requests[2].requestDigest, "Third request digest should match");
63+
assertEq(keccak256("request-digest-1-owner-3"), requests[3].requestDigest, "Fourth request digest should match");
64+
65+
requests = s_registry.getAllowlistedRequests(4, 4);
66+
assertEq(requests.length, 2, "2 requests should be returned");
67+
assertEq(keccak256("request-digest-2-owner-3"), requests[0].requestDigest, "Fifth request digest should match");
68+
assertEq(keccak256("request-digest-3-owner-3"), requests[1].requestDigest, "Sixth request digest should match");
69+
70+
// try out pagination - out of bounds
71+
requests = s_registry.getAllowlistedRequests(8, 4);
72+
assertEq(requests.length, 0, "No requests should be returned");
73+
}
74+
75+
function test_getAllowlistedRequests_WhenSomeOfTheRequestsHaveExpired() external whenSomeRequestsAreAllowlisted {
76+
// it should return only the non-expired requests
77+
_linkTestOwners();
78+
_allowlistValidTestRequests();
79+
80+
vm.warp(currentTimestamp + 1 hours);
81+
uint256 total = s_registry.totalAllowlistedRequests();
82+
// this will time out request-digest-1-owner-1, request-digest-2-owner-1 and request-digest-1-owner-3
83+
vm.warp(currentTimestamp + 1 hours);
84+
WorkflowRegistry.OwnerAllowlistedRequest[] memory requests = s_registry.getAllowlistedRequests(0, 100);
85+
assertEq(total, 6, "Total number of allowlisted requests should be 6");
86+
assertEq(requests.length, 3, "3 requests should be returned");
87+
assertEq(keccak256("request-digest-1-owner-2"), requests[0].requestDigest, "Third request digest should match");
88+
assertEq(keccak256("request-digest-2-owner-3"), requests[1].requestDigest, "Fifth request digest should match");
89+
assertEq(keccak256("request-digest-3-owner-3"), requests[2].requestDigest, "Sixth request digest should match");
90+
91+
vm.warp(currentTimestamp + 2 hours);
92+
total = s_registry.totalAllowlistedRequests();
93+
// this will time out all requests aside from request-digest-3-owner-3
94+
vm.warp(currentTimestamp + 2 hours);
95+
requests = s_registry.getAllowlistedRequests(0, 100);
96+
assertEq(total, 6, "Total number of allowlisted requests should be 6");
97+
assertEq(requests.length, 1, "1 request should be returned");
98+
assertEq(keccak256("request-digest-3-owner-3"), requests[0].requestDigest, "Sixth request digest should match");
99+
100+
vm.warp(currentTimestamp + 3 hours);
101+
total = s_registry.totalAllowlistedRequests();
102+
// this will time out all requests
103+
vm.warp(currentTimestamp + 3 hours);
104+
requests = s_registry.getAllowlistedRequests(0, 100);
105+
assertEq(total, 6, "Total number of allowlisted requests should be 6");
106+
assertEq(requests.length, 0, "No requests should be returned");
107+
}
108+
109+
function _linkTestOwners() internal {
110+
_linkOwner(address(0x1)); // owner1
111+
_linkOwner(address(0x2)); // owner2
112+
_linkOwner(address(0x3)); // owner3
113+
}
114+
115+
// total of 6 valid request digests
116+
function _allowlistValidTestRequests() internal {
117+
// owner1 - 2 request digests
118+
address owner1 = address(0x1);
119+
bytes32 requestDigest = keccak256("request-digest-1-owner-1");
120+
uint32 expiryTimestamp = uint32(currentTimestamp + 1 hours);
121+
vm.prank(owner1);
122+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
123+
requestDigest = keccak256("request-digest-2-owner-1");
124+
vm.prank(owner1);
125+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
126+
127+
// owner2 - 1 request digest
128+
address owner2 = address(0x2);
129+
requestDigest = keccak256("request-digest-1-owner-2");
130+
expiryTimestamp = uint32(currentTimestamp + 2 hours);
131+
vm.prank(owner2);
132+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
133+
134+
// owner3 - 3 request digests
135+
address owner3 = address(0x3);
136+
requestDigest = keccak256("request-digest-1-owner-3");
137+
expiryTimestamp = uint32(currentTimestamp + 1 hours);
138+
vm.prank(owner3);
139+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
140+
requestDigest = keccak256("request-digest-2-owner-3");
141+
expiryTimestamp = uint32(currentTimestamp + 2 hours);
142+
vm.prank(owner3);
143+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
144+
requestDigest = keccak256("request-digest-3-owner-3");
145+
expiryTimestamp = uint32(currentTimestamp + 3 hours);
146+
vm.prank(owner3);
147+
s_registry.allowlistRequest(requestDigest, expiryTimestamp);
148+
}
149+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
WorkflowRegistry.getAllowlistedRequests
2+
├── when no requests are allowlisted
3+
│ └── it should return an empty array
4+
└── when some requests are allowlisted
5+
├── when none of the requests have expired
6+
│ └── it should return all requests
7+
└── when some of the requests have expired
8+
└── it should return only the non-expired requests

gethwrappers/workflow/generated/workflow_registry_wrapper_v2/workflow_registry_wrapper_v2.go

Lines changed: 62 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
GETH_VERSION: 1.15.3
22
capabilities_registry_wrapper_v2: ../../contracts/solc/workflow/dev/v2/CapabilitiesRegistry/CapabilitiesRegistry.sol/CapabilitiesRegistry.abi.json ../../contracts/solc/workflow/dev/v2/CapabilitiesRegistry/CapabilitiesRegistry.sol/CapabilitiesRegistry.bin 69614cd90cc8bcf751f35b6b8ffe3111a535f0aab5676a8aa4b633252ca00549
33
workflow_registry_wrapper_v1: ../../contracts/solc/workflow/v1/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.abi.json ../../contracts/solc/workflow/v1/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.bin 5adc185ac7dabf6f75297855adc0d77e0bfe64af6491de944083fb10f6f9fb06
4-
workflow_registry_wrapper_v2: ../../contracts/solc/workflow/dev/v2/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.abi.json ../../contracts/solc/workflow/dev/v2/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.bin d719c078dd4bed52a65b1c5e22d693b6897733cf60df8a67b889a960427e4cf8
4+
workflow_registry_wrapper_v2: ../../contracts/solc/workflow/dev/v2/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.abi.json ../../contracts/solc/workflow/dev/v2/WorkflowRegistry/WorkflowRegistry.sol/WorkflowRegistry.bin 84294fa58e1acb2812e2b2c2040c9c916f686c85d75627b6e53956d037c15724

0 commit comments

Comments
 (0)