Skip to content

Commit a37a2df

Browse files
committed
Initial work for workflow registry v2: limits
1 parent af40241 commit a37a2df

9 files changed

+480
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.24;
3+
4+
import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
5+
6+
import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
7+
8+
contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion {
9+
string public constant override typeAndVersion = "WorkflowRegistry 2.0.0-dev";
10+
11+
enum WorkflowStatus {
12+
ACTIVE,
13+
PAUSED
14+
}
15+
16+
struct WorkflowMetadata {
17+
bytes32 workflowID; // Unique identifier from hash of owner address, WASM binary content, config content and secrets URL.
18+
bytes32 donLabel; // Label for the DON that is used when distributing the workflow across DONs.
19+
address owner; // ─────────╮ Workflow owner.
20+
uint64 index; // │ Global primary key identifier for the Workflow.
21+
WorkflowStatus status; // ─╯ Current status of the workflow (active, paused).
22+
string workflowName; // Human readable string capped at 64 characters length.
23+
string binaryURL; // URL to the WASM binary.
24+
string configURL; // URL to the config.
25+
string secretsURL; // URL to the encrypted secrets. Workflow DON applies a default refresh period (e.g. daily).
26+
}
27+
28+
/// @dev Global auto‐incrementing counter for next index. Starts at 0.
29+
uint64 private s_nextWorkflowIndex;
30+
31+
constructor() {
32+
// Intialize with default limits for Config.
33+
s_cfg.defaultsPacked = _packDefaults(200, 500, 200);
34+
}
35+
36+
// ================================================================
37+
// | Limits Config |
38+
// ================================================================
39+
40+
/// @dev Instead of a big struct with mappings, we store
41+
/// defaults in a single 32-byte slot, and use nested mappings
42+
/// for overrides.
43+
struct Config {
44+
// Pack three uint32 defaults into a single 96-bit field:
45+
// Layout in bits: [maxUser(0..31) | maxDON(32..63) | maxUserDON(64..95)]
46+
uint96 defaultsPacked;
47+
// 1) user-specific overrides: address -> limit
48+
mapping(address => uint32) userOverride;
49+
// 2) DON-specific overrides: donLabel -> limit
50+
mapping(bytes32 => uint32) donOverride;
51+
// 3) user+don override: user => (donLabel => limit)
52+
mapping(address => mapping(bytes32 => uint32)) userDONOverride;
53+
}
54+
55+
// Our single config instance
56+
Config private s_cfg;
57+
58+
// ─────────────────────────────────────────────────────────────────────────
59+
// Limits Config - External Setters: Owner can set defaults and individual overrides
60+
// ─────────────────────────────────────────────────────────────────────────
61+
62+
/// @notice Update the three default limits in a single call.
63+
function setDefaults(uint32 maxPerUser, uint32 maxPerDON, uint32 maxPerUserDON) external onlyOwner {
64+
s_cfg.defaultsPacked = _packDefaults(maxPerUser, maxPerDON, maxPerUserDON);
65+
}
66+
67+
/// @notice Override the maximum # of workflows a specific user can register.
68+
function setUserOverride(address user, uint32 limit) external onlyOwner {
69+
s_cfg.userOverride[user] = limit;
70+
}
71+
72+
/// @notice Override the maximum # of workflows allowed for a specific DON label
73+
/// @dev donLabel is a bytes32 value of the string, which should not exceed 32 ASCII characters.
74+
function setDONOverride(bytes32 donLabel, uint32 limit) external onlyOwner {
75+
s_cfg.donOverride[donLabel] = limit;
76+
}
77+
78+
/// @notice Override the max # of workflows for a specific (user, DON) pair.
79+
function setUserDONOverride(address user, bytes32 donLabel, uint32 limit) external onlyOwner {
80+
s_cfg.userDONOverride[user][donLabel] = limit;
81+
}
82+
83+
// ─────────────────────────────────────────────────────────────────────────
84+
// Limits Config - Public Getters that return the *effective* limit
85+
// (override if set, else default)
86+
// ─────────────────────────────────────────────────────────────────────────
87+
88+
/// @notice Effective max # of workflows for a particular user.
89+
function getMaxWorkflowsPerUser(
90+
address user
91+
) public view returns (uint32) {
92+
uint32 overrideVal = s_cfg.userOverride[user];
93+
if (overrideVal != 0) {
94+
return overrideVal;
95+
}
96+
// fallback to the default
97+
(uint32 defUser,,) = _unpackDefaults(s_cfg.defaultsPacked);
98+
return defUser;
99+
}
100+
101+
/// @notice Effective max # of workflows for a particular DON.
102+
function getMaxWorkflowsPerDON(
103+
bytes32 donLabel
104+
) public view returns (uint32) {
105+
uint32 overrideVal = s_cfg.donOverride[donLabel];
106+
if (overrideVal != 0) {
107+
return overrideVal;
108+
}
109+
// fallback to the default
110+
(, uint32 defDON,) = _unpackDefaults(s_cfg.defaultsPacked);
111+
return defDON;
112+
}
113+
114+
/// @notice Effective max # of workflows for a (user, DON) combo.
115+
function getMaxWorkflowsPerUserDON(address user, bytes32 donLabel) public view returns (uint32) {
116+
uint32 overrideVal = s_cfg.userDONOverride[user][donLabel];
117+
if (overrideVal != 0) {
118+
return overrideVal;
119+
}
120+
// fallback to the default
121+
(,, uint32 defUserDON) = _unpackDefaults(s_cfg.defaultsPacked);
122+
return defUserDON;
123+
}
124+
125+
/// @notice Returns the three default limits:
126+
/// (maxWorkflowsPerUser, maxWorkflowsPerDon, maxWorkflowsPerUserDon)
127+
function getDefaults() external view returns (uint32 maxPerUser, uint32 maxPerDon, uint32 maxPerUserDon) {
128+
(maxPerUser, maxPerDon, maxPerUserDon) = _unpackDefaults(s_cfg.defaultsPacked);
129+
return (maxPerUser, maxPerDon, maxPerUserDon);
130+
}
131+
132+
// ─────────────────────────────────────────────────────────────────────────
133+
// Limits Config - Internal Helpers: set/read defaults (packed into one 96-bit variable)
134+
// ─────────────────────────────────────────────────────────────────────────
135+
136+
/// @dev Store 3 uint32 values in a single 96-bit field.
137+
function _packDefaults(
138+
uint32 maxPerUser,
139+
uint32 maxPerDON,
140+
uint32 maxPerUserDON
141+
) internal pure returns (uint96 packed) {
142+
// lower 32 bits: maxPerUser
143+
// middle 32 bits: maxPerDON
144+
// top 32 bits: maxPerUserDON
145+
packed = uint96(maxPerUser) | (uint96(maxPerDON) << 32) | (uint96(maxPerUserDON) << 64);
146+
return packed;
147+
}
148+
149+
/// @dev Extract the 3 defaults from the packed value.
150+
function _unpackDefaults(
151+
uint96 packed
152+
) internal pure returns (uint32 maxPerUser, uint32 maxPerDON, uint32 maxPerUserDON) {
153+
maxPerUser = uint32(packed);
154+
maxPerDON = uint32(packed >> 32);
155+
maxPerUserDON = uint32(packed >> 64);
156+
return (maxPerUser, maxPerDON, maxPerUserDON);
157+
}
158+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.24;
3+
4+
contract WorkflowRegistrysetDONOverride {
5+
function test_WhenCallerIsNOTTheOwner() external {
6+
// it should revert with OnlyCallableByOwner
7+
}
8+
9+
modifier whenCallerISTheOwner() {
10+
_;
11+
}
12+
13+
function test_WhenThereAreNoOverridesYet() external whenCallerISTheOwner {
14+
// it is the default value
15+
// call getMaxWorkflowsPerDon(donLabelA) returns the default (e.g. 500)
16+
}
17+
18+
function test_WhenLimitIsSetTo0() external whenCallerISTheOwner {
19+
// it correctly sets the limit to 0
20+
// call setDONOverride(donLabelA, 0)
21+
// getMaxWorkflowsPerDon(donLabelA) returns default (override cleared)
22+
}
23+
24+
function test_WhenLimitIsSetToANormalPositiveValue() external whenCallerISTheOwner {
25+
// it correctly sets the limit
26+
// call setDONOverride(donLabelA, 123)
27+
// getMaxWorkflowsPerDon(donLabelA) returns 123
28+
}
29+
30+
function test_WhenLimitIsSetToUint32Max() external whenCallerISTheOwner {
31+
// it correctly sets the limit
32+
// call setDONOverride(donLabelA, 4294967295)
33+
// getMaxWorkflowsPerDon(donLabelA) returns 4294967295
34+
}
35+
36+
function test_WhenCallingTheFunctionMultipleTimes() external whenCallerISTheOwner {
37+
// it correctly sets the latest value
38+
// call setDONOverride(donLabelA, 10)
39+
// getMaxWorkflowsPerDon(donLabelA) returns 10
40+
// call setDONOverride(donLabelA, 20)
41+
// getMaxWorkflowsPerDon(donLabelA) returns 20 (updated)
42+
}
43+
44+
function test_WhenThereAreMultipleDONs() external whenCallerISTheOwner {
45+
// it should set the correct value for each DON
46+
// setDONOverride(donLabelA, 77)
47+
// getMaxWorkflowsPerDon(donLabelA) returns 77
48+
// getMaxWorkflowsPerDon(donLabelB) returns default (500)
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
WorkflowRegistry.setDONOverride
2+
├── when caller is NOT the owner
3+
│ └── it should revert with OnlyCallableByOwner
4+
└── when caller IS the owner
5+
├── when there are no overrides yet
6+
│ └── it is the default value
7+
│ └── call getMaxWorkflowsPerDon(donLabelA) returns the default (e.g. 500)
8+
├── when limit is set to 0
9+
│ └── it correctly sets the limit to 0
10+
│ ├── call setDONOverride(donLabelA, 0)
11+
│ └── getMaxWorkflowsPerDon(donLabelA) returns default (override cleared)
12+
├── when limit is set to a normal positive value
13+
│ └── it correctly sets the limit
14+
│ ├── call setDONOverride(donLabelA, 123)
15+
│ └── getMaxWorkflowsPerDon(donLabelA) returns 123
16+
├── when limit is set to uint32 max
17+
│ └── it correctly sets the limit
18+
│ ├── call setDONOverride(donLabelA, 4294967295)
19+
│ └── getMaxWorkflowsPerDon(donLabelA) returns 4294967295
20+
├── when calling the function multiple times
21+
│ └── it correctly sets the latest value
22+
│ ├── call setDONOverride(donLabelA, 10)
23+
│ ├── getMaxWorkflowsPerDon(donLabelA) returns 10
24+
│ ├── call setDONOverride(donLabelA, 20)
25+
│ └── getMaxWorkflowsPerDon(donLabelA) returns 20 (updated)
26+
└── when there are multiple DONs
27+
└── it should set the correct value for each DON
28+
├── setDONOverride(donLabelA, 77)
29+
├── getMaxWorkflowsPerDon(donLabelA) returns 77
30+
└── getMaxWorkflowsPerDon(donLabelB) returns default (500)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.24;
3+
4+
contract WorkflowRegistrysetDefaults {
5+
function test_WhenTheCallerIsNOTTheOwner() external {
6+
// it should revert with OnlyCallableByOwner
7+
}
8+
9+
modifier whenTheCallerISTheOwner() {
10+
_;
11+
}
12+
13+
function test_WhenThereAreNoCallsMadeYet() external whenTheCallerISTheOwner {
14+
// it should be the constructor defaults (200, 500, 200)
15+
}
16+
17+
function test_WhenItIsCalledWithATypicalValidUpdate() external whenTheCallerISTheOwner {
18+
// it should correctly set the updated values
19+
// call setDefaults(100, 200, 50)
20+
// getMaxWorkflowsPerUser(...) returns 100
21+
// getMaxWorkflowsPerDon(...) returns 200
22+
// getMaxWorkflowsPerUserDon(...) returns 50
23+
// other mappings/overrides remain untouched
24+
}
25+
26+
function test_WhenAllValuesAreZero() external whenTheCallerISTheOwner {
27+
// it should set 0 for all three values
28+
// call setDefaults(0, 0, 0)
29+
// getMaxWorkflowsPerUser(...) returns 0
30+
// getMaxWorkflowsPerDon(...) returns 0
31+
// getMaxWorkflowsPerUserDon(...) returns 0
32+
}
33+
34+
function test_WhenAllValuesAreAtUint32Max() external whenTheCallerISTheOwner {
35+
// it should set uint32 max for all three values
36+
// call setDefaults(4294967295, 4294967295, 4294967295)
37+
// getMaxWorkflowsPerUser(...) returns 4294967295
38+
// getMaxWorkflowsPerDon(...) returns 4294967295
39+
// getMaxWorkflowsPerUserDon(...) returns 4294967295
40+
}
41+
42+
function test_WhenCalledMultipleTimesInSequence() external whenTheCallerISTheOwner {
43+
// it should set to the most recent values
44+
// call setDefaults(A, B, C)
45+
// getters reflect (A, B, C)
46+
// call setDefaults(D, E, F)
47+
// getters now reflect (D, E, F) (overwriting previous)
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
WorkflowRegistry.setDefaults
2+
├── when the caller is NOT the owner
3+
│ └── it should revert with OnlyCallableByOwner
4+
└── when the caller IS the owner
5+
├── when there are no calls made yet
6+
│ └── it should be the constructor defaults (200, 500, 200)
7+
├── when it is called with a typical valid update
8+
│ └── it should correctly set the updated values
9+
│ ├── call setDefaults(100, 200, 50)
10+
│ │ ├── getMaxWorkflowsPerUser(...) returns 100
11+
│ │ ├── getMaxWorkflowsPerDon(...) returns 200
12+
│ │ └── getMaxWorkflowsPerUserDon(...) returns 50
13+
│ └── other mappings/overrides remain untouched
14+
├── when all values are zero
15+
│ └── it should set 0 for all three values
16+
│ └── call setDefaults(0, 0, 0)
17+
│ ├── getMaxWorkflowsPerUser(...) returns 0
18+
│ ├── getMaxWorkflowsPerDon(...) returns 0
19+
│ └── getMaxWorkflowsPerUserDon(...) returns 0
20+
├── when all values are at uint32 max
21+
│ └── it should set uint32 max for all three values
22+
│ └── call setDefaults(4294967295, 4294967295, 4294967295)
23+
│ ├── getMaxWorkflowsPerUser(...) returns 4294967295
24+
│ ├── getMaxWorkflowsPerDon(...) returns 4294967295
25+
│ └── getMaxWorkflowsPerUserDon(...) returns 4294967295
26+
└── when called multiple times in sequence
27+
└── it should set to the most recent values
28+
├── call setDefaults(A, B, C)
29+
│ └── getters reflect (A, B, C)
30+
└── call setDefaults(D, E, F)
31+
└── getters now reflect (D, E, F) (overwriting previous)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.24;
3+
4+
contract WorkflowRegistrysetUserDONOverride {
5+
function test_WhenCallerIsNOTTheOwner() external {
6+
// it reverts with OnlyCallableByOwner
7+
}
8+
9+
modifier whenCallerISTheOwner() {
10+
_;
11+
}
12+
13+
function test_WhenThereAreNoOverridesYet() external whenCallerISTheOwner {
14+
// it is the default value
15+
// getMaxWorkflowsPerUserDon(user, donLabel) returns the default (e.g. 200)
16+
}
17+
18+
function test_WhenLimitIsSetTo0() external whenCallerISTheOwner {
19+
// it correctly sets the limit to 0
20+
// call setUserDONOverride(user, donLabel, 0)
21+
// getMaxWorkflowsPerUserDon(user, donLabel) returns default (override cleared)
22+
}
23+
24+
function test_WhenLimitIsSetToANormalPositiveValue() external whenCallerISTheOwner {
25+
// it correctly sets the limit
26+
// call setUserDONOverride(user, donLabel, 42)
27+
// getMaxWorkflowsPerUserDon(user, donLabel) returns 42
28+
}
29+
30+
function test_WhenLimitIsUint32Max4294967295() external whenCallerISTheOwner {
31+
// it correctly sets the limit
32+
// call setUserDONOverride(user, donLabel, 4294967295)
33+
// getMaxWorkflowsPerUserDon(user, donLabel) returns 4294967295
34+
}
35+
36+
function test_WhenItIsCalledMultipleTimes() external whenCallerISTheOwner {
37+
// it correctly sets the latest value
38+
// call setUserDONOverride(user, donLabel, 10)
39+
// getMaxWorkflowsPerUserDon(user, donLabel) returns 10
40+
// call setUserDONOverride(user, donLabel, 20)
41+
// getMaxWorkflowsPerUserDon(user, donLabel) returns 20 (updated)
42+
}
43+
44+
function test_WhenThereAreMultipleUsersAndMultipleDONs() external whenCallerISTheOwner {
45+
// it correctly sets the limit for the specific user and DON
46+
// setUserDONOverride(userA, donLabelA, 33)
47+
// getMaxWorkflowsPerUserDon(userA, donLabelA) returns 33
48+
// getMaxWorkflowsPerUserDon(userA, donLabelB) returns default (200)
49+
// getMaxWorkflowsPerUserDon(userB, donLabelA) returns default (200)
50+
}
51+
}

0 commit comments

Comments
 (0)