Skip to content

Commit 28b1d42

Browse files
committed
Initial work for workflow registry v2: limits
1 parent 2910142 commit 28b1d42

10 files changed

+521
-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: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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, bool isSet) external onlyOwner {
71+
if (isSet) {
72+
s_cfg.userOverride[user] = Config.Value(limit, true);
73+
} else {
74+
delete s_cfg.userOverride[user];
75+
}
76+
}
77+
78+
/// @notice Override the maximum # of workflows allowed for a specific DON label.
79+
/// @dev donLabel is a bytes32 value of the string, which should not exceed 32 ASCII characters.
80+
function setDONOverride(bytes32 donLabel, uint32 limit, bool isSet) external onlyOwner {
81+
if (isSet) {
82+
s_cfg.donOverride[donLabel] = Config.Value(limit, true);
83+
} else {
84+
delete s_cfg.donOverride[donLabel];
85+
}
86+
}
87+
88+
/// @notice Override the max # of workflows for a specific (user, DON) pair.
89+
function setUserDONOverride(address user, bytes32 donLabel, uint32 limit, bool isSet) external onlyOwner {
90+
if (isSet) {
91+
s_cfg.userDONOverride[user][donLabel] = Config.Value(limit, true);
92+
} else {
93+
delete s_cfg.userDONOverride[user][donLabel];
94+
}
95+
}
96+
97+
// ─────────────────────────────────────────────────────────────────────────
98+
// Limits Config - Public Getters that return the *effective* limit
99+
// (override if set, else default)
100+
// ─────────────────────────────────────────────────────────────────────────
101+
102+
/// @notice Effective max # of workflows for a particular user.
103+
function getMaxWorkflowsPerUser(
104+
address user
105+
) public view returns (uint32) {
106+
Config.Value memory override = s_cfg.userOverride[user];
107+
if (override.isSet) {
108+
return override.value;
109+
}
110+
// fallback to the default
111+
(uint32 defUser,,) = _unpackDefaults(s_cfg.defaultsPacked);
112+
return defUser;
113+
}
114+
115+
/// @notice Effective max # of workflows for a particular DON.
116+
function getMaxWorkflowsPerDON(
117+
bytes32 donLabel
118+
) public view returns (uint32) {
119+
Config.Value memory override = s_cfg.donOverride[donLabel];
120+
if (override.isSet) {
121+
return override.value;
122+
}
123+
// fallback to the default
124+
(, uint32 defDON,) = _unpackDefaults(s_cfg.defaultsPacked);
125+
return defDON;
126+
}
127+
128+
/// @notice Effective max # of workflows for a (user, DON) combo.
129+
function getMaxWorkflowsPerUserDON(address user, bytes32 donLabel) public view returns (uint32) {
130+
Config.Value memory override = s_cfg.userDONOverride[user][donLabel];
131+
if (override.isSet) {
132+
return override.value;
133+
}
134+
// fallback to the default
135+
(,, uint32 defUserDON) = _unpackDefaults(s_cfg.defaultsPacked);
136+
return defUserDON;
137+
}
138+
139+
/// @notice Returns the three default limits:
140+
/// (maxWorkflowsPerUser, maxWorkflowsPerDon, maxWorkflowsPerUserDon)
141+
function getDefaults() external view returns (uint32 maxPerUser, uint32 maxPerDon, uint32 maxPerUserDon) {
142+
(maxPerUser, maxPerDon, maxPerUserDon) = _unpackDefaults(s_cfg.defaultsPacked);
143+
return (maxPerUser, maxPerDon, maxPerUserDon);
144+
}
145+
146+
// ─────────────────────────────────────────────────────────────────────────
147+
// Limits Config - Internal Helpers: set/read defaults (packed into one 96-bit variable)
148+
// ─────────────────────────────────────────────────────────────────────────
149+
150+
/// @dev Store 3 uint32 values in a single 96-bit field.
151+
function _packDefaults(
152+
uint32 maxPerUser,
153+
uint32 maxPerDON,
154+
uint32 maxPerUserDON
155+
) internal pure returns (uint96 packed) {
156+
// lower 32 bits: maxPerUser
157+
// middle 32 bits: maxPerDON
158+
// top 32 bits: maxPerUserDON
159+
packed = uint96(maxPerUser) | (uint96(maxPerDON) << 32) | (uint96(maxPerUserDON) << 64);
160+
return packed;
161+
}
162+
163+
/// @dev Extract the 3 defaults from the packed value.
164+
function _unpackDefaults(
165+
uint96 packed
166+
) internal pure returns (uint32 maxPerUser, uint32 maxPerDON, uint32 maxPerUserDON) {
167+
maxPerUser = uint32(packed);
168+
maxPerDON = uint32(packed >> 32);
169+
maxPerUserDON = uint32(packed >> 64);
170+
return (maxPerUser, maxPerDON, maxPerUserDON);
171+
}
172+
}
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)