Closed jfschwarz closed 1 year ago
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Updated |
---|---|---|---|
zodiac-roles-app | ❌ Failed (Inspect) | Mar 17, 2023 at 1:43PM (UTC) |
I will be commenting here as I factor in each point
O1 - discarded
O2 - ✅
O3 - is missing in action
O5 - ✅
O4 - discussing
O6 - ✅
O7 - deferred
Right now more focused on optimising PermissionCheck vs PermissionBuilding. Former is recurrent overhead, latter is a one time thing
Regarding O8
I see what you're saying. So I figure your idea is to remove the check from scopeFunction and instead make integrity a public entry point that is validated by the SDK before calling scopeFunction. While this approach would reduce gas costs in the PermissionBuilder flow, it's worth considering whether we should also enforce the check.
By including the check, we can create a more secure and air tight system that always operates on validated permission data. Without the check, the PermissionChecker and Loader flows would enter the real of totally unpredictable behavior when called with permission data that violates integrity. Therefore, it may be wise to include the check in addition to making integrity a public entry point.
Note: the integrity check cost will always be dwarfed by storage write costs, anyway
Issues
none found
Optimizations
O1. WriteOnce.sol
store
function usescreate
. It should usecreate2
, so that identical parameter configs are stored only once (globally).O2. WriteOnce.sol
store
andcreationCodeFor
both useabi.encodePacked
. As you pointed out, it can be reduced to a single call to save gas.(We should compare notes, I ended up with this implementation for Mech: https://github.com/gnosis/mech/blob/main/contracts/libraries/Bytecode.sol.)
O4. PermissionLoader.sol / PermissionChecker.sol
Let's avoid doing this half-baked allowance check in
_withinAllowance()
and instead only record the consumption/trace and then rely on the final, proper check in PermissionBuilder's_track()
.The check is half-baked, because it reads stale storage values in two scenarios:
if the call is to the roles mod's exec function (reentrency)
This will allow us to get rid of the
_loadAllowances
function in PermissionChecker for some gas gains. We can also remove theallowance
field from theParameterConfig
struct.O5. PermissionChecker.sol
revertWith()
should be internal or private.O6. ScopeConfig.sol
packing
(2 bits per param) could be killed. It's implicitly defined by the value ofcomparison
, provided we do the following unification: We always hash compValues forEqualTo
. And: We never hash compValue for anything else (which is already the case).O7. Core.sol
Role assignments could be stored as a bitmap for minimizing storage used if an account is assigned multiple roles. Proof-of-concept on branch
poc-assignment-bitmap
, benchmark results:It makes the membership check a bit more expensive, by about 50 gas, though. So maybe let's just keep it like it is.
O8. Integrity.sol
We might consider expanding the integrity checks on condition trees (e.g. assert parent <= own id). As adding more checks will increase gas, we could add an unchecked variant for the
scopeFunction
, e.g.scopeFunctionUnchecked
. That way people can run checks in advance via static calls and save some gas on the real apply.Alternatively, we could consider removing all integrity checks and leaving this aspect entirely to the client (Roles SDK). This would be my preference.
Nits & Names
N1. General – Pragmas
Most contracts have
pragma solidity >=0.7.0 <0.9.0;
. We use custom errors which are supported only since v0.8.4 and some type conversions also are not allowed on earlier solidity versions.Suggestion: update pragmas to
pragma solidity >=0.8.17 <0.9.0;
N2. PermissionChecker.sol – Traces
Why do we need to track the entire ParameterConfig? It should be enough to track the allowance ID.
Trace.value
is of typebytes32
, it should rather be casted touint256
directly."Trace" is not a good name. It took me a bit to realize that the sole purpose of traces is compiling a list of used allowances. I assumed it would "trace" the entire path through the scope config tree.
Proposal:
_track(trace)
could become_digest(consumed)
. It reverts 🤮 if too much, otherwise stores in stomachrage.N3. General – ScopeConfig/ParameterConfig
The names "scope config" and "parameter config" used somewhat interchangeably. Suggestion:
ParameterConfig
toCondition
ParameterConfigFlat
toConditionFlat
ScopeConfig
toConditionPacking
N4. PermissionLoader.sol / ScopeConfig.sol
The separation of concerns between these is a bit fuzzy. It could be sharpened by moving all functions concerned with packing and unpacking to the ScopeConfig (ConditionPacking) lib. This would leave the following functions in PermissionLoader:
_store
,_load
,._loadAllowances
Alternatively, just move everything into
PermissionLoader
. (Name not optimal, because it not only loads but also stores.)N5. Types.sol – ExecutionOptions (super nit)
In my taste, two separate booleans would be better:
boolean send
andboolean delegatecall
(1 bit each), instead ofenum { None, Send, DelegateCall, Both }
with 2 bits.