Skip to content

Commit 6e6ec4a

Browse files
committed
Permission Serialization/Deserialization (#394)
* First sketch of the new Serialization architecture * Patch EqualToAvatar conditions * Extract permission validation and writing into an external library bundle * Make the unpackers single loop, remove double pass * Optimize unpacker gas usage by reducing array accesses * Isolate Function loading from contract storage in new lib * Refactor: explicitly separate function storage and loading * fix avatar storage slot offset when patching * Consolidate three packer libraries into unified FunctionPacker * Reorganize permission storage into serialize/deserialize structure and consolidate unpacker files * rename permission-storage to scoped-function and consolidate supporting files * Remove PermissionLoader abstraction layer * remove uncessary constant from ImmutableStorage * improve scopeconfig comment * Clean up Deserializer and remove unused constants from Unpacker * Optimize unpacker by inlining node memory loads --------- Co-authored-by: Cristóvão <cristovaoth@users.noreply.github.com>
1 parent bde0467 commit 6e6ec4a

33 files changed

+1880
-670
lines changed

packages/evm/contracts/AllowanceTracker.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import "./_Core.sol";
77
* @title AllowanceTracker - a component of the Zodiac Roles Mod that is
88
* responsible for loading and calculating allowance balances. Persists
99
* consumptions back to storage.
10-
* @author Cristóvão Honorato - <cristovao.honorato@gnosis.io>
11-
* @author Jan-Felix Schwarz - <jan-felix.schwarz@gnosis.io>
1210
*/
1311
abstract contract AllowanceTracker is Core {
1412
event ConsumeAllowance(

packages/evm/contracts/PermissionBuilder.sol

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22
pragma solidity >=0.8.17 <0.9.0;
33

44
import "./_Core.sol";
5-
import "./Integrity.sol";
65

7-
import "./packers/BufferPacker.sol";
8-
9-
/**
10-
* @title PermissionBuilder - a component of the Zodiac Roles Mod that is
11-
* responsible for constructing, managing, granting, and revoking all types
12-
* of permission data.
13-
* @author Cristóvão Honorato - <cristovao.honorato@gnosis.io>
14-
* @author Jan-Felix Schwarz - <jan-felix.schwarz@gnosis.io>
6+
import "./scoped-function/serialize/Serializer.sol";
7+
import "./scoped-function/ScopeConfig.sol";
8+
9+
/*
10+
* The Permission Model Hierarchy
11+
*
12+
* Role → Target (address)
13+
* ├─ ALLOWED ──→ No rules, blanket approval (just a flag)
14+
* ├─ DISALLOWED ──→ No rules, blanket denial (just a flag)
15+
* └─ SCOPED ──→ Per-function rules (THIS is permission-storage)
16+
* ├─ function1 → [conditions...]
17+
* ├─ function2 → [conditions...]
18+
* └─ function3 → [conditions...]
1519
*/
1620
abstract contract PermissionBuilder is Core {
1721
event AllowTarget(
@@ -105,8 +109,8 @@ abstract contract PermissionBuilder is Core {
105109
bytes4 selector,
106110
ExecutionOptions options
107111
) external onlyOwner {
108-
roles[roleKey].scopeConfig[_key(targetAddress, selector)] = BufferPacker
109-
.packHeaderAsWildcarded(options);
112+
roles[roleKey].scopeConfig[_key(targetAddress, selector)] = ScopeConfig
113+
.packAsWildcarded(options);
110114

111115
emit AllowFunction(roleKey, targetAddress, selector, options);
112116
}
@@ -137,14 +141,8 @@ abstract contract PermissionBuilder is Core {
137141
ConditionFlat[] memory conditions,
138142
ExecutionOptions options
139143
) external onlyOwner {
140-
Integrity.enforce(conditions);
141-
142-
_store(
143-
roles[roleKey],
144-
_key(targetAddress, selector),
145-
conditions,
146-
options
147-
);
144+
roles[roleKey].scopeConfig[_key(targetAddress, selector)] = Serializer
145+
.store(conditions, options);
148146

149147
emit ScopeFunction(
150148
roleKey,

packages/evm/contracts/PermissionChecker.sol

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import "./_Periphery.sol";
66
import "./AbiDecoder.sol";
77
import "./Consumptions.sol";
88

9-
import "./packers/BufferPacker.sol";
9+
import "./scoped-function/deserialize/Deserializer.sol";
10+
import "./scoped-function/ScopeConfig.sol";
1011

1112
/**
1213
* @title PermissionChecker - a component of Zodiac Roles Mod responsible
1314
* for enforcing and authorizing actions performed on behalf of a role.
1415
*
15-
* @author Cristóvão Honorato - <cristovao.honorato@gnosis.io>
16-
* @author Jan-Felix Schwarz - <jan-felix.schwarz@gnosis.io>
16+
* @author gnosisguild
17+
*
1718
*/
1819
abstract contract PermissionChecker is Core, Periphery {
1920
function _authorize(
@@ -122,9 +123,8 @@ abstract contract PermissionChecker is Core, Periphery {
122123
}
123124

124125
if (role.targets[to].clearance == Clearance.Function) {
125-
bytes32 key = _key(to, bytes4(data));
126+
bytes32 header = role.scopeConfig[_key(to, bytes4(data))];
126127
{
127-
bytes32 header = role.scopeConfig[key];
128128
if (header == 0) {
129129
return (
130130
Status.FunctionNotAllowed,
@@ -135,8 +135,8 @@ abstract contract PermissionChecker is Core, Periphery {
135135
);
136136
}
137137

138-
(bool isWildcarded, ExecutionOptions options) = BufferPacker
139-
.unpackOptions(header);
138+
(bool isWildcarded, ExecutionOptions options, ) = ScopeConfig
139+
.unpack(header);
140140

141141
Status status = _executionOptions(value, operation, options);
142142
if (status != Status.Ok) {
@@ -156,8 +156,7 @@ abstract contract PermissionChecker is Core, Periphery {
156156

157157
return
158158
_scopedFunction(
159-
role,
160-
key,
159+
header,
161160
data,
162161
Context({
163162
to: to,
@@ -210,16 +209,28 @@ abstract contract PermissionChecker is Core, Periphery {
210209
}
211210

212211
function _scopedFunction(
213-
Role storage role,
214-
bytes32 key,
212+
bytes32 scopeConfig,
215213
bytes calldata data,
216214
Context memory context
217215
) private view returns (Status, Result memory) {
216+
Consumption[] memory consumptions;
218217
(
219218
Condition memory condition,
220219
TypeTree memory typeTree,
221-
Consumption[] memory consumptions
222-
) = _load(role, key);
220+
bytes32[] memory allowanceKeys
221+
) = Deserializer.load(scopeConfig);
222+
223+
if (allowanceKeys.length > 0) {
224+
consumptions = new Consumption[](allowanceKeys.length);
225+
226+
for (uint256 i; i < allowanceKeys.length; ++i) {
227+
consumptions[i].allowanceKey = allowanceKeys[i];
228+
(consumptions[i].balance, ) = _accruedAllowance(
229+
allowances[allowanceKeys[i]],
230+
uint64(block.timestamp)
231+
);
232+
}
233+
}
223234

224235
Payload memory payload = AbiDecoder.inspect(data, typeTree);
225236

@@ -512,20 +523,17 @@ abstract contract PermissionChecker is Core, Periphery {
512523
Context memory context
513524
) private view returns (Status, Result memory result) {
514525
result.consumptions = context.consumptions;
515-
516526
if (
517527
payload.children.length == 0 ||
518528
payload.children.length > condition.children.length
519529
) {
520530
return (Status.ParameterNotSubsetOfAllowed, result);
521531
}
522-
523532
uint256 taken;
524533
for (uint256 i; i < payload.children.length; ++i) {
525534
bool found = false;
526535
for (uint256 j; j < condition.children.length; ++j) {
527536
if (taken & (1 << j) != 0) continue;
528-
529537
(Status status, Result memory _result) = _walk(
530538
data,
531539
condition.children[j],
@@ -551,7 +559,6 @@ abstract contract PermissionChecker is Core, Periphery {
551559
);
552560
}
553561
}
554-
555562
return (Status.Ok, result);
556563
}
557564

packages/evm/contracts/PermissionLoader.sol

Lines changed: 0 additions & 165 deletions
This file was deleted.

packages/evm/contracts/Roles.sol

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,19 @@ pragma solidity >=0.8.17 <0.9.0;
44
import "./AllowanceTracker.sol";
55
import "./PermissionBuilder.sol";
66
import "./PermissionChecker.sol";
7-
import "./PermissionLoader.sol";
87

98
/**
109
* @title Zodiac Roles Mod - granular, role-based, access control for your
11-
* on-chain avatar accounts (like Safe).
12-
* @author Cristóvão Honorato - <cristovao.honorato@gnosis.io>
13-
* @author Jan-Felix Schwarz - <jan-felix.schwarz@gnosis.io>
14-
* @author Auryn Macmillan - <auryn.macmillan@gnosis.io>
15-
* @author Nathan Ginnever - <nathan.ginnever@gnosis.io>
10+
* on-chain avatar accounts.
11+
*
12+
* @author gnosisguild
13+
*
1614
*/
1715
contract Roles is
1816
Modifier,
1917
AllowanceTracker,
2018
PermissionBuilder,
21-
PermissionChecker,
22-
PermissionLoader
19+
PermissionChecker
2320
{
2421
mapping(address => bytes32) public defaultRoles;
2522

0 commit comments

Comments
 (0)