Skip to content

Commit 0530b5e

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

10 files changed

+524
-0
lines changed

contracts/gas-snapshots/workflow.gas-snapshot

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,33 @@ WorkflowRegistry_registerWorkflow:test_WhenTheWorkflowInputsAreAllValid() (gas:
5656
WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsInAnAllowedDON() (gas: 936092)
5757
WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsNotInAnAllowedDON() (gas: 510822)
5858
WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsNotAnAuthorizedAddress() (gas: 509176)
59+
WorkflowRegistry_setDONOverride:test_WhenCallerIsNOTTheOwner() (gas: 120)
60+
WorkflowRegistry_setDONOverride:test_WhenCallingTheFunctionMultipleTimes() (gas: 208)
61+
WorkflowRegistry_setDONOverride:test_WhenLimitIsSetTo0() (gas: 186)
62+
WorkflowRegistry_setDONOverride:test_WhenLimitIsSetToANormalPositiveValue() (gas: 230)
63+
WorkflowRegistry_setDONOverride:test_WhenLimitIsSetToUint32Max() (gas: 249)
64+
WorkflowRegistry_setDONOverride:test_WhenThereAreMultipleDONs() (gas: 164)
65+
WorkflowRegistry_setDONOverride:test_WhenThereAreNoOverridesYet() (gas: 142)
66+
WorkflowRegistry_setDefaults:test_WhenAllValuesAreAtUint32Max() (gas: 208)
67+
WorkflowRegistry_setDefaults:test_WhenAllValuesAreZero() (gas: 120)
68+
WorkflowRegistry_setDefaults:test_WhenCalledMultipleTimesInSequence() (gas: 164)
69+
WorkflowRegistry_setDefaults:test_WhenItIsCalledWithATypicalValidUpdate() (gas: 142)
70+
WorkflowRegistry_setDefaults:test_WhenTheCallerIsNOTTheOwner() (gas: 227)
71+
WorkflowRegistry_setDefaults:test_WhenThereAreNoCallsMadeYet() (gas: 186)
72+
WorkflowRegistry_setUserDONOverride:test_WhenCallerIsNOTTheOwner() (gas: 142)
73+
WorkflowRegistry_setUserDONOverride:test_WhenItIsCalledMultipleTimes() (gas: 208)
74+
WorkflowRegistry_setUserDONOverride:test_WhenLimitIsSetTo0() (gas: 186)
75+
WorkflowRegistry_setUserDONOverride:test_WhenLimitIsSetToANormalPositiveValue() (gas: 230)
76+
WorkflowRegistry_setUserDONOverride:test_WhenLimitIsUint32Max4294967295() (gas: 120)
77+
WorkflowRegistry_setUserDONOverride:test_WhenThereAreMultipleUsersAndMultipleDONs() (gas: 249)
78+
WorkflowRegistry_setUserDONOverride:test_WhenThereAreNoOverridesYet() (gas: 164)
79+
WorkflowRegistry_setUserOverride:test_WhenCallerIsNOTTheOwner() (gas: 120)
80+
WorkflowRegistry_setUserOverride:test_WhenLimitIsSetTo0() (gas: 186)
81+
WorkflowRegistry_setUserOverride:test_WhenLimitIsSetToANormalPositiveValue() (gas: 208)
82+
WorkflowRegistry_setUserOverride:test_WhenLimitIsSetToUint32Max4294967295() (gas: 249)
83+
WorkflowRegistry_setUserOverride:test_WhenTheFunctionIsCalledMultipleTimes() (gas: 230)
84+
WorkflowRegistry_setUserOverride:test_WhenThereAreMultipleUsers() (gas: 142)
85+
WorkflowRegistry_setUserOverride:test_WhenThereAreNoOverridesYet() (gas: 164)
5986
WorkflowRegistry_unlockRegistry:test_WhenTheCallerIsTheContractOwner() (gas: 30325)
6087
WorkflowRegistry_updateAllowedDONs:test_WhenTheBoolInputIsFalse() (gas: 29739)
6188
WorkflowRegistry_updateAllowedDONs:test_WhenTheBoolInputIsTrue() (gas: 170256)
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// SPDX-License-Identifier: BUSL 1.1
2+
pragma solidity 0.8.26;
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 created_at; // │ block.timestamp when the workflow was first registered.
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+
constructor() {
29+
// Intialize with default limits for Config.
30+
s_cfg.defaultsPacked = _packDefaults(200, 500, 200);
31+
}
32+
33+
// ================================================================
34+
// | Limits Config |
35+
// ================================================================
36+
37+
/// @dev Instead of a big struct with mappings, we store
38+
/// defaults in a single 32-byte slot, and use nested mappings
39+
/// for overrides.
40+
struct Config {
41+
// Pack three uint32 defaults into a single 96-bit field:
42+
// Layout in bits: [maxUser(0..31) | maxDON(32..63) | maxUserDON(64..95)]
43+
uint96 defaultsPacked;
44+
// Struct to distinguish between unset and explicitly set zero values
45+
struct Value {
46+
uint32 value;
47+
bool isSet;
48+
}
49+
// 1) user-specific overrides: address -> limit
50+
mapping(address => Value) userOverride;
51+
// 2) DON-specific overrides: donLabel -> limit
52+
mapping(bytes32 => Value) donOverride;
53+
// 3) user+don override: user => (donLabel => limit)
54+
mapping(address => mapping(bytes32 => Value)) userDONOverride;
55+
}
56+
57+
// Our single config instance
58+
Config private s_cfg;
59+
60+
// ─────────────────────────────────────────────────────────────────────────
61+
// Limits Config - External Setters: Owner can set defaults and individual overrides
62+
// ─────────────────────────────────────────────────────────────────────────
63+
64+
/// @notice Update the three default limits in a single call.
65+
function setDefaults(uint32 maxPerUser, uint32 maxPerDON, uint32 maxPerUserDON) external onlyOwner {
66+
s_cfg.defaultsPacked = _packDefaults(maxPerUser, maxPerDON, maxPerUserDON);
67+
}
68+
69+
/// @notice Override the maximum # of workflows a specific user can register.
70+
function setUserOverride(address user, uint32 limit) external onlyOwner {
71+
s_cfg.userOverride[user] = Config.Value(limit, true);
72+
}
73+
74+
/// @notice Remove the override for a specific user, returning to default limits.
75+
function removeUserOverride(address user) external onlyOwner {
76+
delete s_cfg.userOverride[user];
77+
}
78+
79+
/// @notice Override the maximum # of workflows allowed for a specific DON label
80+
/// @dev donLabel is a bytes32 value of the string, which should not exceed 32 ASCII characters.
81+
function setDONOverride(bytes32 donLabel, uint32 limit) external onlyOwner {
82+
s_cfg.donOverride[donLabel] = Config.Value(limit, true);
83+
}
84+
85+
/// @notice Remove the override for a specific DON, returning to default limits.
86+
function removeDONOverride(bytes32 donLabel) external onlyOwner {
87+
delete s_cfg.donOverride[donLabel];
88+
}
89+
90+
/// @notice Override the max # of workflows for a specific (user, DON) pair.
91+
function setUserDONOverride(address user, bytes32 donLabel, uint32 limit) external onlyOwner {
92+
s_cfg.userDONOverride[user][donLabel] = Config.Value(limit, true);
93+
}
94+
95+
/// @notice Remove the override for a specific (user, DON) pair, returning to default limits.
96+
function removeUserDONOverride(address user, bytes32 donLabel) external onlyOwner {
97+
delete s_cfg.userDONOverride[user][donLabel];
98+
}
99+
100+
// ─────────────────────────────────────────────────────────────────────────
101+
// Limits Config - Public Getters that return the *effective* limit
102+
// (override if set, else default)
103+
// ─────────────────────────────────────────────────────────────────────────
104+
105+
/// @notice Effective max # of workflows for a particular user.
106+
function getMaxWorkflowsPerUser(
107+
address user
108+
) public view returns (uint32) {
109+
Config.Value memory override = s_cfg.userOverride[user];
110+
if (override.isSet) {
111+
return override.value;
112+
}
113+
// fallback to the default
114+
(uint32 defUser,,) = _unpackDefaults(s_cfg.defaultsPacked);
115+
return defUser;
116+
}
117+
118+
/// @notice Effective max # of workflows for a particular DON.
119+
function getMaxWorkflowsPerDON(
120+
bytes32 donLabel
121+
) public view returns (uint32) {
122+
Config.Value memory override = s_cfg.donOverride[donLabel];
123+
if (override.isSet) {
124+
return override.value;
125+
}
126+
// fallback to the default
127+
(, uint32 defDON,) = _unpackDefaults(s_cfg.defaultsPacked);
128+
return defDON;
129+
}
130+
131+
/// @notice Effective max # of workflows for a (user, DON) combo.
132+
function getMaxWorkflowsPerUserDON(address user, bytes32 donLabel) public view returns (uint32) {
133+
Config.Value memory override = s_cfg.userDONOverride[user][donLabel];
134+
if (override.isSet) {
135+
return override.value;
136+
}
137+
// fallback to the default
138+
(,, uint32 defUserDON) = _unpackDefaults(s_cfg.defaultsPacked);
139+
return defUserDON;
140+
}
141+
142+
/// @notice Returns the three default limits:
143+
/// (maxWorkflowsPerUser, maxWorkflowsPerDon, maxWorkflowsPerUserDon)
144+
function getDefaults() external view returns (uint32 maxPerUser, uint32 maxPerDon, uint32 maxPerUserDon) {
145+
(maxPerUser, maxPerDon, maxPerUserDon) = _unpackDefaults(s_cfg.defaultsPacked);
146+
return (maxPerUser, maxPerDon, maxPerUserDon);
147+
}
148+
149+
// ─────────────────────────────────────────────────────────────────────────
150+
// Limits Config - Internal Helpers: set/read defaults (packed into one 96-bit variable)
151+
// ─────────────────────────────────────────────────────────────────────────
152+
153+
/// @dev Store 3 uint32 values in a single 96-bit field.
154+
function _packDefaults(
155+
uint32 maxPerUser,
156+
uint32 maxPerDON,
157+
uint32 maxPerUserDON
158+
) internal pure returns (uint96 packed) {
159+
// lower 32 bits: maxPerUser
160+
// middle 32 bits: maxPerDON
161+
// top 32 bits: maxPerUserDON
162+
packed = uint96(maxPerUser) | (uint96(maxPerDON) << 32) | (uint96(maxPerUserDON) << 64);
163+
return packed;
164+
}
165+
166+
/// @dev Extract the 3 defaults from the packed value.
167+
function _unpackDefaults(
168+
uint96 packed
169+
) internal pure returns (uint32 maxPerUser, uint32 maxPerDON, uint32 maxPerUserDON) {
170+
maxPerUser = uint32(packed);
171+
maxPerDON = uint32(packed >> 32);
172+
maxPerUserDON = uint32(packed >> 64);
173+
return (maxPerUser, maxPerDON, maxPerUserDON);
174+
}
175+
}
Lines changed: 50 additions & 0 deletions
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 WorkflowRegistry_setDONOverride {
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+
}
Lines changed: 30 additions & 0 deletions
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 for a DON label
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)
Lines changed: 49 additions & 0 deletions
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 WorkflowRegistry_setDefaults {
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+
}
Lines changed: 31 additions & 0 deletions
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)

0 commit comments

Comments
 (0)